feat: 建立Vue3前端框架
This commit is contained in:
17
README.md
17
README.md
@@ -87,6 +87,22 @@ spring:
|
||||
|
||||
当前阶段还没有加入 Web 服务依赖或常驻任务,所以应用可能启动成功后立即退出。
|
||||
|
||||
启动前端:
|
||||
|
||||
```powershell
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
前端检查:
|
||||
|
||||
```powershell
|
||||
cd frontend
|
||||
npm run type-check
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 规划模块
|
||||
|
||||
- `agent-core`:Agent 执行模型、工具注册、记忆和编排能力。
|
||||
@@ -106,4 +122,3 @@ spring:
|
||||
- [Spring AI Reference](https://docs.spring.io/spring-ai/reference/)
|
||||
- [Spring AI RAG Reference](https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html)
|
||||
- [MyBatis-Plus](https://baomidou.com/)
|
||||
|
||||
|
||||
5
frontend/.gitignore
vendored
Normal file
5
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
*.local
|
||||
|
||||
1
frontend/env.d.ts
vendored
Normal file
1
frontend/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
12
frontend/index.html
Normal file
12
frontend/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Common Agent</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
3345
frontend/package-lock.json
generated
Normal file
3345
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
frontend/package.json
Normal file
32
frontend/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "common-agent-frontend",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0",
|
||||
"test:unit": "vitest run",
|
||||
"type-check": "vue-tsc -b"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"axios": "^1.13.2",
|
||||
"element-plus": "^2.11.8",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"@types/node": "^24.10.2",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"jsdom": "^27.2.0",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.2.7",
|
||||
"vitest": "^4.0.15",
|
||||
"vue-tsc": "^3.1.8"
|
||||
}
|
||||
}
|
||||
3
frontend/src/App.vue
Normal file
3
frontend/src/App.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
9
frontend/src/api/__tests__/request.spec.ts
Normal file
9
frontend/src/api/__tests__/request.spec.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { request } from '../request';
|
||||
|
||||
describe('request client', () => {
|
||||
it('uses the backend api prefix', () => {
|
||||
expect(request.defaults.baseURL).toBe('/api');
|
||||
});
|
||||
});
|
||||
21
frontend/src/api/request.ts
Normal file
21
frontend/src/api/request.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
|
||||
export interface RequestResult<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export const request = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
export function get<T>(url: string, config?: AxiosRequestConfig) {
|
||||
return request.get<RequestResult<T>>(url, config).then((response) => response.data);
|
||||
}
|
||||
|
||||
export function post<T, D = unknown>(url: string, data?: D, config?: AxiosRequestConfig) {
|
||||
return request.post<RequestResult<T>>(url, data, config).then((response) => response.data);
|
||||
}
|
||||
62
frontend/src/layouts/AdminLayout.vue
Normal file
62
frontend/src/layouts/AdminLayout.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Box,
|
||||
Collection,
|
||||
DataBoard,
|
||||
Document,
|
||||
Files,
|
||||
Grid,
|
||||
} 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 = [
|
||||
{ path: '/dashboard', label: '工作台', icon: DataBoard },
|
||||
{ path: '/system/enums', label: '系统枚举', icon: Grid },
|
||||
{ path: '/system/attachments', label: '附件管理', icon: Files },
|
||||
{ path: '/rag/stores', label: '知识库', icon: Collection },
|
||||
{ path: '/rag/documents', label: '知识文档', icon: Document },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container class="admin-layout">
|
||||
<el-aside class="admin-sidebar" width="232px">
|
||||
<div class="brand">
|
||||
<el-icon :size="24">
|
||||
<Box />
|
||||
</el-icon>
|
||||
<span>Common Agent</span>
|
||||
</div>
|
||||
|
||||
<el-menu class="side-menu" :default-active="$route.path" router>
|
||||
<el-menu-item v-for="item in menuItems" :key="item.path" :index="item.path">
|
||||
<el-icon>
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
<span>{{ item.label }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-container>
|
||||
<el-header class="admin-header">
|
||||
<div>
|
||||
<h1>{{ pageTitle }}</h1>
|
||||
<p>{{ appStore.environmentName }}</p>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-main class="admin-main">
|
||||
<RouterView />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
17
frontend/src/main.ts
Normal file
17
frontend/src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'element-plus/dist/index.css';
|
||||
import './styles/global.css';
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.use(ElementPlus);
|
||||
|
||||
app.mount('#app');
|
||||
8
frontend/src/pages/DashboardPage.vue
Normal file
8
frontend/src/pages/DashboardPage.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<section class="page-panel">
|
||||
<div class="page-panel__header">
|
||||
<h2>工作台</h2>
|
||||
<span>Console</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
6
frontend/src/pages/NotFoundPage.vue
Normal file
6
frontend/src/pages/NotFoundPage.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<main class="not-found">
|
||||
<h1>404</h1>
|
||||
<RouterLink to="/dashboard">返回工作台</RouterLink>
|
||||
</main>
|
||||
</template>
|
||||
8
frontend/src/pages/RagDocumentsPage.vue
Normal file
8
frontend/src/pages/RagDocumentsPage.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<section class="page-panel">
|
||||
<div class="page-panel__header">
|
||||
<h2>知识文档</h2>
|
||||
<span>Documents</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
8
frontend/src/pages/RagStoresPage.vue
Normal file
8
frontend/src/pages/RagStoresPage.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<section class="page-panel">
|
||||
<div class="page-panel__header">
|
||||
<h2>知识库</h2>
|
||||
<span>RAG</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
8
frontend/src/pages/SystemAttachmentsPage.vue
Normal file
8
frontend/src/pages/SystemAttachmentsPage.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<section class="page-panel">
|
||||
<div class="page-panel__header">
|
||||
<h2>附件管理</h2>
|
||||
<span>Files</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
8
frontend/src/pages/SystemEnumsPage.vue
Normal file
8
frontend/src/pages/SystemEnumsPage.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<section class="page-panel">
|
||||
<div class="page-panel__header">
|
||||
<h2>系统枚举</h2>
|
||||
<span>System</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
17
frontend/src/router/__tests__/router.spec.ts
Normal file
17
frontend/src/router/__tests__/router.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { routes } from '../index';
|
||||
|
||||
describe('router', () => {
|
||||
it('defines the admin shell routes', () => {
|
||||
const paths = routes.map((route) => route.path);
|
||||
|
||||
expect(paths).toContain('/');
|
||||
expect(paths).toContain('/dashboard');
|
||||
expect(paths).toContain('/system/enums');
|
||||
expect(paths).toContain('/system/attachments');
|
||||
expect(paths).toContain('/rag/stores');
|
||||
expect(paths).toContain('/rag/documents');
|
||||
expect(paths).toContain('/:pathMatch(.*)*');
|
||||
});
|
||||
});
|
||||
109
frontend/src/router/index.ts
Normal file
109
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
import DashboardPage from '@/pages/DashboardPage.vue';
|
||||
import NotFoundPage from '@/pages/NotFoundPage.vue';
|
||||
import RagDocumentsPage from '@/pages/RagDocumentsPage.vue';
|
||||
import RagStoresPage from '@/pages/RagStoresPage.vue';
|
||||
import SystemAttachmentsPage from '@/pages/SystemAttachmentsPage.vue';
|
||||
import SystemEnumsPage from '@/pages/SystemEnumsPage.vue';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: DashboardPage,
|
||||
meta: { title: '工作台' },
|
||||
},
|
||||
{
|
||||
path: '/system/enums',
|
||||
name: 'system-enums',
|
||||
component: SystemEnumsPage,
|
||||
meta: { title: '系统枚举' },
|
||||
},
|
||||
{
|
||||
path: '/system/attachments',
|
||||
name: 'system-attachments',
|
||||
component: SystemAttachmentsPage,
|
||||
meta: { title: '附件管理' },
|
||||
},
|
||||
{
|
||||
path: '/rag/stores',
|
||||
name: 'rag-stores',
|
||||
component: RagStoresPage,
|
||||
meta: { title: '知识库' },
|
||||
},
|
||||
{
|
||||
path: '/rag/documents',
|
||||
name: 'rag-documents',
|
||||
component: RagDocumentsPage,
|
||||
meta: { title: '知识文档' },
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: NotFoundPage,
|
||||
meta: { title: '页面不存在' },
|
||||
},
|
||||
];
|
||||
|
||||
const routerRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: AdminLayout,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
name: 'dashboard',
|
||||
component: DashboardPage,
|
||||
meta: { title: '工作台' },
|
||||
},
|
||||
{
|
||||
path: 'system/enums',
|
||||
name: 'system-enums',
|
||||
component: SystemEnumsPage,
|
||||
meta: { title: '系统枚举' },
|
||||
},
|
||||
{
|
||||
path: 'system/attachments',
|
||||
name: 'system-attachments',
|
||||
component: SystemAttachmentsPage,
|
||||
meta: { title: '附件管理' },
|
||||
},
|
||||
{
|
||||
path: 'rag/stores',
|
||||
name: 'rag-stores',
|
||||
component: RagStoresPage,
|
||||
meta: { title: '知识库' },
|
||||
},
|
||||
{
|
||||
path: 'rag/documents',
|
||||
name: 'rag-documents',
|
||||
component: RagDocumentsPage,
|
||||
meta: { title: '知识文档' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: NotFoundPage,
|
||||
meta: { title: '页面不存在' },
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: routerRoutes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
7
frontend/src/stores/app.ts
Normal file
7
frontend/src/stores/app.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
environmentName: '本地开发',
|
||||
}),
|
||||
});
|
||||
128
frontend/src/styles/global.css
Normal file
128
frontend/src/styles/global.css
Normal file
@@ -0,0 +1,128 @@
|
||||
:root {
|
||||
color: #1f2937;
|
||||
background: #f4f6f8;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 320px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.admin-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
border-right: 1px solid #d9dee7;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 64px;
|
||||
padding: 0 18px;
|
||||
border-bottom: 1px solid #e5e9f0;
|
||||
color: #182433;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.side-menu {
|
||||
border-right: 0;
|
||||
padding: 10px 8px;
|
||||
}
|
||||
|
||||
.side-menu .el-menu-item {
|
||||
height: 42px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
border-bottom: 1px solid #d9dee7;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
margin: 0;
|
||||
color: #172033;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.admin-header p {
|
||||
margin: 4px 0 0;
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-main {
|
||||
padding: 20px;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.page-panel {
|
||||
min-height: calc(100vh - 104px);
|
||||
border: 1px solid #d9dee7;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.page-panel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 56px;
|
||||
padding: 0 18px;
|
||||
border-bottom: 1px solid #edf0f4;
|
||||
}
|
||||
|
||||
.page-panel__header h2 {
|
||||
margin: 0;
|
||||
color: #1f2937;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.page-panel__header span {
|
||||
color: #768195;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
display: grid;
|
||||
min-height: 100vh;
|
||||
place-content: center;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.not-found h1 {
|
||||
margin: 0;
|
||||
font-size: 48px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
12
frontend/tsconfig.app.json
Normal file
12
frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"types": ["vite/client"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"]
|
||||
}
|
||||
11
frontend/tsconfig.json
Normal file
11
frontend/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
frontend/tsconfig.node.json
Normal file
17
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
25
frontend/vite.config.ts
Normal file
25
frontend/vite.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user