优化前端控制台样式并更新文档
This commit is contained in:
23
AGENT.md
23
AGENT.md
@@ -82,7 +82,25 @@
|
|||||||
|
|
||||||
### 3.4 管理控制台模块
|
### 3.4 管理控制台模块
|
||||||
|
|
||||||
后续需要一个前端控制台,至少覆盖:
|
当前已经建立基于 Vue 3、Vite、Element Plus 的前端控制台基础骨架。
|
||||||
|
|
||||||
|
已具备的页面与布局:
|
||||||
|
|
||||||
|
- 左侧管理菜单与品牌区
|
||||||
|
- 工作台入口
|
||||||
|
- 系统枚举管理页
|
||||||
|
- 附件管理入口
|
||||||
|
- 知识库入口
|
||||||
|
- 知识文档入口
|
||||||
|
|
||||||
|
当前样式约定:
|
||||||
|
|
||||||
|
- 管理控制台定位为后台工具界面,优先保证信息密度、可扫描性和稳定布局。
|
||||||
|
- 主内容区域只保留页面自身标题,避免外层布局和页面面板重复展示标题。
|
||||||
|
- 页面统一使用 `page-panel` 作为内容容器,侧边栏、页面面板、工具栏和表格使用统一边框、背景和主色变量。
|
||||||
|
- 系统枚举页工具栏采用响应式布局,桌面端保持查询项和操作按钮分区,窄屏时纵向排列。
|
||||||
|
|
||||||
|
后续控制台至少继续覆盖:
|
||||||
|
|
||||||
- 枚举管理
|
- 枚举管理
|
||||||
- 附件管理
|
- 附件管理
|
||||||
@@ -159,6 +177,8 @@
|
|||||||
|
|
||||||
6. 补前端控制台
|
6. 补前端控制台
|
||||||
|
|
||||||
|
当前前端控制台已经完成基础骨架和系统枚举页面样式优化,后续重点应转向附件、知识库、知识文档页面的业务闭环。
|
||||||
|
|
||||||
## 7. 下一步建议
|
## 7. 下一步建议
|
||||||
|
|
||||||
结合当前代码状态,接下来建议重点做:
|
结合当前代码状态,接下来建议重点做:
|
||||||
@@ -168,6 +188,7 @@
|
|||||||
- 完善 `rag_store` / `rag_document` 的增删改查
|
- 完善 `rag_store` / `rag_document` 的增删改查
|
||||||
- 增加知识库文档上传并自动关联附件
|
- 增加知识库文档上传并自动关联附件
|
||||||
- 为后续切片与向量化预留任务入口
|
- 为后续切片与向量化预留任务入口
|
||||||
|
- 补齐前端附件、知识库、知识文档页面的表单、列表和接口联调
|
||||||
|
|
||||||
## 8. 文档用途说明
|
## 8. 文档用途说明
|
||||||
|
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -3,8 +3,8 @@
|
|||||||
Common Agent 是一个规划中的通用 Agent 平台,技术路线基于 Java、Spring Boot 和 Spring AI。
|
Common Agent 是一个规划中的通用 Agent 平台,技术路线基于 Java、Spring Boot 和 Spring AI。
|
||||||
项目目标是建设一套完整的前后端系统,支持 Agent 编排、工具调用、会话管理、RAG 知识库和平台管理能力。
|
项目目标是建设一套完整的前后端系统,支持 Agent 编排、工具调用、会话管理、RAG 知识库和平台管理能力。
|
||||||
|
|
||||||
当前项目处于基础工程阶段。后端骨架、PostgreSQL 配置、MyBatis-Plus、Lombok 和多环境配置文件已经完成;
|
当前项目处于基础工程阶段。后端骨架、PostgreSQL 配置、MyBatis-Plus、Lombok、多环境配置文件和前端控制台基础页面已经完成;
|
||||||
Agent 运行时、RAG 索引、前端页面和管理功能会在后续阶段逐步实现。
|
Agent 运行时、RAG 索引和更多管理功能会在后续阶段逐步实现。
|
||||||
|
|
||||||
## 项目愿景
|
## 项目愿景
|
||||||
|
|
||||||
@@ -31,6 +31,11 @@ Common Agent 希望成为一个可复用的企业级 AI 应用基础平台:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
common_agent
|
common_agent
|
||||||
|
├── frontend
|
||||||
|
│ ├── src/layouts
|
||||||
|
│ ├── src/pages
|
||||||
|
│ ├── src/router
|
||||||
|
│ └── src/styles
|
||||||
├── src/main/java/com/bruce
|
├── src/main/java/com/bruce
|
||||||
│ └── CommonAgentApplication.java
|
│ └── CommonAgentApplication.java
|
||||||
├── src/main/resources
|
├── src/main/resources
|
||||||
@@ -99,10 +104,30 @@ npm run dev
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cd frontend
|
cd frontend
|
||||||
|
npm run test:unit
|
||||||
npm run type-check
|
npm run type-check
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 前端控制台
|
||||||
|
|
||||||
|
前端控制台位于 `frontend` 目录,当前使用 Vue 3、Vite、Pinia、Vue Router 和 Element Plus。
|
||||||
|
|
||||||
|
当前已有页面:
|
||||||
|
|
||||||
|
- 工作台
|
||||||
|
- 系统枚举
|
||||||
|
- 附件管理
|
||||||
|
- 知识库
|
||||||
|
- 知识文档
|
||||||
|
|
||||||
|
当前 UI 规范:
|
||||||
|
|
||||||
|
- 左侧为固定管理导航,右侧为主内容区。
|
||||||
|
- 主内容区不再额外渲染外层页面标题,标题由各业务页面面板自行展示。
|
||||||
|
- 全局样式集中在 `frontend/src/styles/global.css`,页面专属样式优先放在对应 `.vue` 文件的 scoped style 中。
|
||||||
|
- 后台页面以清晰、克制、便于扫描为优先目标,避免营销式大面积装饰。
|
||||||
|
|
||||||
## 规划模块
|
## 规划模块
|
||||||
|
|
||||||
- `agent-core`:Agent 执行模型、工具注册、记忆和编排能力。
|
- `agent-core`:Agent 执行模型、工具注册、记忆和编排能力。
|
||||||
|
|||||||
@@ -7,15 +7,6 @@ import {
|
|||||||
Files,
|
Files,
|
||||||
Grid,
|
Grid,
|
||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
import { useAppStore } from '@/stores/app';
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const pageTitle = computed(() => String(route.meta.title ?? 'Common Agent'));
|
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ path: '/dashboard', label: '工作台', icon: DataBoard },
|
{ path: '/dashboard', label: '工作台', icon: DataBoard },
|
||||||
@@ -47,13 +38,6 @@ const menuItems = [
|
|||||||
</el-aside>
|
</el-aside>
|
||||||
|
|
||||||
<el-container>
|
<el-container>
|
||||||
<el-header class="admin-header">
|
|
||||||
<div>
|
|
||||||
<h1>{{ pageTitle }}</h1>
|
|
||||||
<p>{{ appStore.environmentName }}</p>
|
|
||||||
</div>
|
|
||||||
</el-header>
|
|
||||||
|
|
||||||
<el-main class="admin-main">
|
<el-main class="admin-main">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|||||||
29
frontend/src/layouts/__tests__/AdminLayout.spec.ts
Normal file
29
frontend/src/layouts/__tests__/AdminLayout.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import ElementPlus from 'element-plus';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import AdminLayout from '../AdminLayout.vue';
|
||||||
|
|
||||||
|
vi.mock('vue-router', () => ({
|
||||||
|
useRoute: () => ({ meta: { title: '系统枚举' } }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AdminLayout', () => {
|
||||||
|
it('does not render a duplicate page header above the main page content', () => {
|
||||||
|
const wrapper = mount(AdminLayout, {
|
||||||
|
global: {
|
||||||
|
plugins: [createPinia(), ElementPlus],
|
||||||
|
mocks: {
|
||||||
|
$route: { path: '/system/enums' },
|
||||||
|
},
|
||||||
|
stubs: {
|
||||||
|
RouterView: { template: '<main data-test="router-view" />' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('.admin-header').exists()).toBe(false);
|
||||||
|
expect(wrapper.find('[data-test="router-view"]').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -358,28 +358,58 @@ onMounted(loadEnums);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.enum-toolbar {
|
.enum-toolbar {
|
||||||
display: flex;
|
display: grid;
|
||||||
align-items: flex-start;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
justify-content: space-between;
|
align-items: start;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 16px 18px 4px;
|
padding: 18px 22px 8px;
|
||||||
|
border-bottom: 1px solid #eef2f7;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-toolbar :deep(.el-form) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enum-toolbar :deep(.el-form-item) {
|
.enum-toolbar :deep(.el-form-item) {
|
||||||
margin-bottom: 12px;
|
margin-right: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-toolbar :deep(.el-form-item__label) {
|
||||||
|
color: #475467;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enum-toolbar :deep(.el-input) {
|
.enum-toolbar :deep(.el-input) {
|
||||||
width: 180px;
|
width: 196px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enum-table {
|
.enum-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.enum-table :deep(.el-table__header-wrapper th) {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #667085;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-table :deep(.el-table__row td) {
|
||||||
|
color: #344054;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-table :deep(.el-table__row:hover td) {
|
||||||
|
background: #f8fbff;
|
||||||
|
}
|
||||||
|
|
||||||
.enum-actions {
|
.enum-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
padding-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.batch-base {
|
.batch-base {
|
||||||
@@ -401,4 +431,37 @@ onMounted(loadEnums);
|
|||||||
.batch-table {
|
.batch-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1080px) {
|
||||||
|
.enum-toolbar {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-actions {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.enum-toolbar {
|
||||||
|
padding: 14px 16px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-toolbar :deep(.el-form) {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-toolbar :deep(.el-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-actions {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-base {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
:root {
|
:root {
|
||||||
|
--app-bg: #f5f7fb;
|
||||||
|
--app-border: #e3e8f0;
|
||||||
|
--app-border-soft: #eef2f7;
|
||||||
|
--app-primary: #1677ff;
|
||||||
|
--app-primary-soft: #eaf3ff;
|
||||||
|
--app-text: #172033;
|
||||||
|
--app-text-muted: #667085;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
background: #f4f6f8;
|
background: var(--app-bg);
|
||||||
font-family:
|
font-family:
|
||||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
sans-serif;
|
sans-serif;
|
||||||
@@ -27,90 +34,96 @@ a {
|
|||||||
|
|
||||||
.admin-layout {
|
.admin-layout {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
background: var(--app-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-sidebar {
|
.admin-sidebar {
|
||||||
border-right: 1px solid #d9dee7;
|
border-right: 1px solid var(--app-border);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
box-shadow: 1px 0 0 rgba(15, 23, 42, 0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
height: 64px;
|
height: 72px;
|
||||||
padding: 0 18px;
|
padding: 0 22px;
|
||||||
border-bottom: 1px solid #e5e9f0;
|
border-bottom: 1px solid var(--app-border-soft);
|
||||||
color: #182433;
|
color: var(--app-text);
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand .el-icon {
|
||||||
|
color: var(--app-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.side-menu {
|
.side-menu {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
padding: 10px 8px;
|
padding: 14px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu .el-menu-item {
|
.side-menu .el-menu-item {
|
||||||
height: 42px;
|
height: 44px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
color: #344054;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-header {
|
.side-menu .el-menu-item:hover {
|
||||||
display: flex;
|
background: #f3f7fd;
|
||||||
align-items: center;
|
|
||||||
height: 64px;
|
|
||||||
border-bottom: 1px solid #d9dee7;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-header h1 {
|
.side-menu .el-menu-item.is-active {
|
||||||
margin: 0;
|
background: var(--app-primary-soft);
|
||||||
color: #172033;
|
color: var(--app-primary);
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-header p {
|
.side-menu .el-menu-item .el-icon {
|
||||||
margin: 4px 0 0;
|
font-size: 18px;
|
||||||
color: #667085;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-main {
|
.admin-main {
|
||||||
padding: 20px;
|
min-width: 0;
|
||||||
background: #f4f6f8;
|
padding: 24px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0) 180px),
|
||||||
|
var(--app-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-panel {
|
.page-panel {
|
||||||
min-height: calc(100vh - 104px);
|
min-height: calc(100vh - 48px);
|
||||||
border: 1px solid #d9dee7;
|
border: 1px solid var(--app-border);
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-panel__header {
|
.page-panel__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-height: 56px;
|
min-height: 64px;
|
||||||
padding: 0 18px;
|
padding: 0 22px;
|
||||||
border-bottom: 1px solid #edf0f4;
|
border-bottom: 1px solid var(--app-border-soft);
|
||||||
|
background: linear-gradient(180deg, #ffffff, #fbfcff);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-panel__header h2 {
|
.page-panel__header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1f2937;
|
color: var(--app-text);
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-panel__header span {
|
.page-panel__header span {
|
||||||
color: #768195;
|
color: var(--app-text-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-found {
|
.not-found {
|
||||||
@@ -126,3 +139,43 @@ a {
|
|||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.admin-layout {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-sidebar {
|
||||||
|
width: 100% !important;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 1px solid var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu .el-menu-item {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin: 0 6px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-main {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-panel {
|
||||||
|
min-height: calc(100vh - 148px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-panel__header {
|
||||||
|
min-height: 58px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user