From 5455490811f4dc3c3842633f51660108b2420491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=BC=A0?= <2091066548@qq.com> Date: Tue, 14 Oct 2025 17:33:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90AI=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增AI知识库管理功能模块 - 实现知识库的增删改查功能 - 实现文档管理(文本/文件/链接) - 实现向量化功能和测试 - 支持卡片式布局展示 - 完整的响应式设计 - 修复所有TypeScript类型错误 - 添加依赖:marked@^16.4.0, less@^4.2.2 - 打包测试通过 主要文件: - src/views/teacher/ai-knowledge-naive-ui/ - AI知识库管理模块 - docs/ - 相关API文档 - package.json - 新增依赖配置 --- .augment/rules/在线学习平台.md | 3 +- docs/ai-model-api-debug.md | 148 ++++ docs/ai-model-dict-api.md | 234 ++++++ docs/dynamic-menu-routes.md | 197 +++++ package-lock.json | 217 +++++ package.json | 2 + .../AiKnowledgeBaseList.vue | 540 ++++++++++++ .../ai-knowledge-naive-ui/DEPLOYMENT.md | 314 +++++++ .../teacher/ai-knowledge-naive-ui/README.md | 186 +++++ .../ai-knowledge-naive-ui/api/knowledge.ts | 193 +++++ .../components/KnowledgeBaseModal.vue | 244 ++++++ .../components/KnowledgeDocListModal.vue | 775 ++++++++++++++++++ .../components/KnowledgeDocTextModal.vue | 347 ++++++++ .../components/TextDescModal.vue | 222 +++++ .../ai-knowledge-naive-ui/example/App.vue | 88 ++ .../teacher/ai-knowledge-naive-ui/index.ts | 23 + .../ai-knowledge-naive-ui/package.json | 58 ++ .../ai-knowledge-naive-ui/tsconfig.json | 46 ++ .../ai-knowledge-naive-ui/tsconfig.node.json | 11 + .../ai-knowledge-naive-ui/types/knowledge.ts | 174 ++++ .../ai-knowledge-naive-ui/utils/clipboard.ts | 48 ++ .../ai-knowledge-naive-ui/utils/common.ts | 173 ++++ .../ai-knowledge-naive-ui/utils/file.ts | 128 +++ .../ai-knowledge-naive-ui/utils/http.ts | 203 +++++ .../ai-knowledge-naive-ui/vite.config.ts | 50 ++ 25 files changed, 4623 insertions(+), 1 deletion(-) create mode 100644 docs/ai-model-api-debug.md create mode 100644 docs/ai-model-dict-api.md create mode 100644 docs/dynamic-menu-routes.md create mode 100644 src/views/teacher/ai-knowledge-naive-ui/AiKnowledgeBaseList.vue create mode 100644 src/views/teacher/ai-knowledge-naive-ui/DEPLOYMENT.md create mode 100644 src/views/teacher/ai-knowledge-naive-ui/README.md create mode 100644 src/views/teacher/ai-knowledge-naive-ui/api/knowledge.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeBaseModal.vue create mode 100644 src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocListModal.vue create mode 100644 src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocTextModal.vue create mode 100644 src/views/teacher/ai-knowledge-naive-ui/components/TextDescModal.vue create mode 100644 src/views/teacher/ai-knowledge-naive-ui/example/App.vue create mode 100644 src/views/teacher/ai-knowledge-naive-ui/index.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/package.json create mode 100644 src/views/teacher/ai-knowledge-naive-ui/tsconfig.json create mode 100644 src/views/teacher/ai-knowledge-naive-ui/tsconfig.node.json create mode 100644 src/views/teacher/ai-knowledge-naive-ui/types/knowledge.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/utils/clipboard.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/utils/common.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/utils/file.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/utils/http.ts create mode 100644 src/views/teacher/ai-knowledge-naive-ui/vite.config.ts diff --git a/.augment/rules/在线学习平台.md b/.augment/rules/在线学习平台.md index 4f73a27..9d71d2b 100644 --- a/.augment/rules/在线学习平台.md +++ b/.augment/rules/在线学习平台.md @@ -1,5 +1,6 @@ --- -type: "manual" +type: "always_apply" +description: "Example description" --- 1、在接下来的每一个步骤当中,请帮我实现对页面的响应式设计 diff --git a/docs/ai-model-api-debug.md b/docs/ai-model-api-debug.md new file mode 100644 index 0000000..485fb49 --- /dev/null +++ b/docs/ai-model-api-debug.md @@ -0,0 +1,148 @@ +# AI模型字典API调试指南 + +## 问题描述 + +在调用AI模型字典API时遇到错误: +``` +Sign签名校验失败,时间戳为空! +``` + +## 已实施的修复措施 + +### 1. **添加时间戳参数到URL** +```typescript +// 修改前 +return request.get('/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20\'LLM\',name,id') + +// 修改后 +const timestamp = Date.now() +return request.get(`/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20'LLM',name,id?_t=${timestamp}`) +``` + +### 2. **增强请求头时间戳** +```typescript +// 添加多种时间戳格式 +const timestamp = Date.now().toString() +config.headers['X-Request-Time'] = timestamp +config.headers['timestamp'] = timestamp +config.headers['X-Timestamp'] = timestamp +``` + +### 3. **增强错误调试** +- 在AiAppSetting.vue中添加详细的调试日志 +- 在AiModelTest.vue中添加直接请求测试功能 +- 显示完整的错误信息和响应数据 + +## 测试步骤 + +### 1. 访问测试页面 +``` +http://localhost:5173/ai-model-test +``` + +### 2. 检查登录状态 +- 确保用户已登录并有有效的token +- 在测试页面中查看"用户Token"状态 + +### 3. 测试API调用 +1. 点击"加载AI模型列表"按钮(使用封装的API) +2. 点击"直接请求测试"按钮(使用原生axios) +3. 查看控制台日志和响应数据 + +### 4. 检查AI应用设置 +1. 访问 `/ai/app` 页面 +2. 点击任意AI应用卡片 +3. 切换到"模型配置"选项卡 +4. 查看控制台日志 + +## 调试信息 + +### 请求信息 +- **URL**: `/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20'LLM',name,id?_t={timestamp}` +- **方法**: GET +- **Base URL**: `/jeecgboot` (或环境变量配置) + +### 请求头 +```javascript +{ + 'Content-Type': 'application/json', + 'X-Access-Token': '{user_token}', + 'X-Request-Time': '{timestamp}', + 'timestamp': '{timestamp}', + 'X-Timestamp': '{timestamp}' +} +``` + +### 预期响应格式 +```json +{ + "success": true, + "message": "", + "code": 0, + "result": [ + { + "value": "1890232564262739969", + "text": "OpenAI", + "color": null, + "jsonObject": null, + "label": "OpenAI", + "title": "OpenAI" + } + ], + "timestamp": 1759136645858 +} +``` + +## 可能的解决方案 + +### 1. **检查后端签名算法** +如果问题仍然存在,可能需要: +- 检查后端期望的签名算法 +- 添加必要的签名参数 +- 确认时间戳格式要求 + +### 2. **检查接口权限** +- 确认该接口是否需要特定的用户权限 +- 检查token是否有效 +- 验证用户角色权限 + +### 3. **检查接口路径** +- 确认接口路径是否正确 +- 检查URL编码是否正确 +- 验证查询参数格式 + +## 备用方案 + +如果API调用仍然失败,系统会自动使用备用数据: +```typescript +modelOptions.value = [ + { label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' }, + { label: 'GPT-4', value: 'gpt-4' }, + { label: 'Claude-3', value: 'claude-3' } +] +``` + +## 下一步调试 + +1. **查看网络请求** + - 打开浏览器开发者工具 + - 查看Network选项卡 + - 检查实际发送的请求和响应 + +2. **检查后端日志** + - 查看后端服务器日志 + - 确认请求是否到达后端 + - 检查签名验证逻辑 + +3. **联系后端开发** + - 确认接口的正确调用方式 + - 获取签名算法详细信息 + - 确认必需的请求参数 + +## 文件修改记录 + +- `src/api/modules/system.ts`: 添加字典API和时间戳参数 +- `src/api/request.ts`: 增强时间戳请求头 +- `src/views/Ai/component/AiAppSetting.vue`: 添加详细调试日志 +- `src/views/AiModelTest.vue`: 创建专门的测试页面 +- `src/router/index.ts`: 添加测试页面路由 diff --git a/docs/ai-model-dict-api.md b/docs/ai-model-dict-api.md new file mode 100644 index 0000000..94cac41 --- /dev/null +++ b/docs/ai-model-dict-api.md @@ -0,0 +1,234 @@ +# AI模型字典API集成 + +## 概述 + +本文档说明如何在AI应用设置弹窗中集成真实的AI模型字典API,替换原有的模拟数据。 + +## API接口信息 + +### 获取AI模型字典 +- **接口地址**: `/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20'LLM',name,id` +- **请求方法**: GET +- **接口说明**: 获取LLM类型的AI模型字典数据 + +### 响应格式 +```json +{ + "success": true, + "message": "", + "code": 0, + "result": [ + { + "value": "1890232564262739969", + "text": "OpenAI", + "color": null, + "jsonObject": null, + "label": "OpenAI", + "title": "OpenAI" + }, + { + "value": "1897481367743143938", + "text": "deepseek", + "color": null, + "jsonObject": null, + "label": "deepseek", + "title": "deepseek" + }, + { + "value": "1897883052995006466", + "text": "智谱", + "color": null, + "jsonObject": null, + "label": "智谱", + "title": "智谱" + }, + { + "value": "1970031008335876097", + "text": "测试", + "color": null, + "jsonObject": null, + "label": "测试", + "title": "测试" + } + ], + "timestamp": 1759136645858 +} +``` + +## 实现的功能 + +### 1. **API模块扩展** (`src/api/modules/system.ts`) + +添加了字典相关的API接口: + +```typescript +// 字典项接口 +export interface DictItem { + value: string + text: string + color: string | null + jsonObject: any | null + label: string + title: string +} + +export const SystemApi = { + // 获取字典项 + getDictItems(dictCode: string, params?: string): Promise> + + // 获取AI模型字典项(LLM类型) + getAiModelDict(): Promise> +} +``` + +### 2. **AI应用设置组件更新** (`src/views/Ai/component/AiAppSetting.vue`) + +修改了 `loadModelOptions` 函数: + +```typescript +// 加载模型选项 +const loadModelOptions = async () => { + loadingModels.value = true + try { + const response = await SystemApi.getAiModelDict() + if (response.data.code === 200 || response.data.code === 0) { + modelOptions.value = response.data.data.map(item => ({ + label: item.text, + value: item.value + })) + console.log('✅ AI模型列表加载成功:', modelOptions.value) + } else { + throw new Error(response.data.message || '获取模型列表失败') + } + } catch (error: any) { + console.error('❌ 加载模型列表失败:', error) + message.error(error.message || '加载模型列表失败') + + // 失败时使用备用数据 + modelOptions.value = [ + { label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' }, + { label: 'GPT-4', value: 'gpt-4' }, + { label: 'Claude-3', value: 'claude-3' } + ] + } finally { + loadingModels.value = false + } +} +``` + +### 3. **测试页面** (`src/views/AiModelTest.vue`) + +创建了专门的测试页面来验证API调用: +- 实时加载AI模型数据 +- 显示API响应状态 +- 测试选择器功能 +- 查看原始数据 + +## 使用流程 + +### 1. 点击AI应用卡片 +在AI应用列表页面,点击任意应用卡片: +```vue + +``` + +### 2. 打开设置弹窗 +系统会调用 `handleEditApp` 函数,打开应用设置弹窗: +```typescript +const handleEditApp = (app: AiApp) => { + isEditMode.value = true + currentApp.value = { ...app } + showSettingModal.value = true +} +``` + +### 3. 自动加载模型数据 +弹窗打开时,会自动调用 `loadModelOptions()` 函数加载AI模型数据: +```typescript +onMounted(() => { + loadModelOptions() // 自动加载模型选项 + loadFlowOptions() + loadSelectedKnowledges() +}) +``` + +### 4. 显示模型选择器 +在"模型配置"选项卡中,用户可以看到从后端加载的真实AI模型列表: +- OpenAI +- deepseek +- 智谱 +- 测试 + +## 测试方法 + +### 1. 访问测试页面 +``` +http://localhost:5173/ai-model-test +``` + +### 2. 测试功能 +- 点击"加载AI模型列表"按钮 +- 查看加载状态和结果 +- 测试选择器功能 +- 查看原始API响应数据 + +### 3. 验证AI应用设置 +- 访问 `/ai/app` 页面 +- 点击任意AI应用卡片 +- 在弹窗中切换到"模型配置"选项卡 +- 验证AI模型下拉选择器是否显示真实数据 + +## 错误处理 + +系统包含完整的错误处理机制: + +1. **网络请求失败**: 显示错误消息并使用备用数据 +2. **API响应错误**: 解析错误信息并提示用户 +3. **数据格式错误**: 容错处理,确保界面正常显示 +4. **加载状态**: 显示加载指示器,提升用户体验 + +## 数据映射 + +API返回的字典项会被映射为选择器选项: + +```typescript +// API数据格式 +{ + "value": "1890232564262739969", + "text": "OpenAI", + "label": "OpenAI", + "title": "OpenAI" +} + +// 映射为选择器选项 +{ + label: "OpenAI", // 显示文本 + value: "1890232564262739969" // 选择值 +} +``` + +## 扩展说明 + +### 添加其他字典类型 +可以在 `SystemApi` 中添加其他字典类型的获取方法: + +```typescript +// 获取其他类型的字典 +getOtherDict(): Promise> { + return request.get('/sys/dict/getDictItems/other_dict_code') +} +``` + +### 自定义字典查询 +使用通用的 `getDictItems` 方法: + +```typescript +// 自定义查询条件 +const response = await SystemApi.getDictItems('dict_code', 'custom_params') +``` + +这样就完成了AI模型字典API的集成,用户在AI应用设置弹窗中可以看到真实的AI模型数据了! diff --git a/docs/dynamic-menu-routes.md b/docs/dynamic-menu-routes.md new file mode 100644 index 0000000..f44418e --- /dev/null +++ b/docs/dynamic-menu-routes.md @@ -0,0 +1,197 @@ +# 动态菜单路由系统 + +## 概述 + +本系统实现了基于后端接口的动态菜单路由加载功能,支持从后端获取菜单配置并自动生成前端路由。 + +## 接口说明 + +### 获取首页菜单 +- **接口**: `/aiol/aiolMenu/getIndexMenus` +- **方法**: GET +- **返回格式**: +```json +{ + "success": true, + "message": "", + "code": 200, + "result": [ + { + "id": "1", + "name": "首页", + "path": "/", + "type": "index", + "icon": null, + "parentId": null, + "sortOrder": 99, + "izVisible": 1, + "permissionKey": null + } + ], + "timestamp": 1759134778382 +} +``` + +### 获取学生菜单 +- **接口**: `/aiol/aiolMenu/getStudentMenus` +- **方法**: GET +- **返回格式**: 同上,type为"student" + +## 核心文件 + +### 1. API模块 (`src/api/modules/menu.ts`) +```typescript +export interface MenuItem { + id: string + name: string + path: string + type: string + icon: string | null + parentId: string | null + sortOrder: number + izVisible: number + permissionKey: string | null +} + +export class MenuApi { + static async getIndexMenus(): Promise> + static async getStudentMenus(): Promise> +} +``` + +### 2. 路由工具 (`src/utils/routeUtils.ts`) +```typescript +// 根据菜单数据生成路由配置 +export function generateRoutesFromMenus(menus: MenuItem[]): RouteRecordRaw[] + +// 生成导航菜单数据 +export function generateNavMenus(menus: MenuItem[]) + +// 检查路由是否存在于菜单中 +export function isRouteInMenus(path: string, menus: MenuItem[]): boolean +``` + +### 3. 状态管理 (`src/stores/menu.ts`) +```typescript +export const useMenuStore = defineStore('menu', () => { + // 状态 + const indexMenus = ref([]) + const studentMenus = ref([]) + const loading = ref(false) + const error = ref(null) + + // 计算属性 + const navMenus = computed(() => generateNavMenus(indexMenus.value)) + const visibleIndexMenus = computed(() => ...) + + // 方法 + const fetchIndexMenus = async () => { ... } + const fetchStudentMenus = async () => { ... } +}) +``` + +### 4. 路由配置 (`src/router/index.ts`) +在路由守卫中自动加载动态菜单: +```typescript +router.beforeEach(async (to, from, next) => { + // 加载动态菜单路由(仅在首次访问时) + if (!isMenuLoaded) { + await loadMenuRoutes() + } + // ... +}) +``` + +## 使用方法 + +### 1. 在组件中使用菜单状态 +```vue + +``` + +### 2. 使用动态导航组件 +```vue + + + +``` + +### 3. 检查路由权限 +```typescript +import { useMenuStore } from '@/stores/menu' + +const menuStore = useMenuStore() + +// 检查路由是否可见 +const isVisible = menuStore.isRouteVisible('/courses', 'index') +``` + +## 路径映射 + +当前支持的路径映射: + +| 菜单名称 | 路径 | 组件 | +|---------|------|------| +| 首页 | / | Home | +| 热门好课 | /courses | Courses | +| 专题训练 | /special-training | SpecialTraining | +| 师资力量 | /faculty | Faculty | +| 精选资源 | /resources | Resources | +| 活动 | /activities | Activities | +| AI体验 | /ai/app | AiAppList | + +## 测试页面 + +访问 `/menu-test` 可以查看动态菜单系统的测试页面,包含: +- 菜单状态监控 +- 菜单数据展示 +- 路由跳转测试 +- 动态导航组件演示 + +## 响应式设计 + +动态导航组件支持响应式设计: +- 桌面端:垂直菜单布局 +- 移动端:水平滚动菜单布局 + +## 错误处理 + +系统包含完整的错误处理机制: +- 网络请求失败重试 +- 加载状态显示 +- 错误信息提示 +- 手动重试功能 + +## 扩展说明 + +### 添加新的路径映射 +在 `src/utils/routeUtils.ts` 中的 `componentMap` 添加新的路径映射: +```typescript +const componentMap: Record Promise> = { + '/new-path': () => import('@/views/NewComponent.vue'), + // ... +} +``` + +### 自定义菜单图标 +在 `DynamicNavigation.vue` 中的 `iconMap` 添加新的图标映射: +```typescript +const iconMap: Record = { + 'new-icon': NewIconComponent, + // ... +} +``` diff --git a/package-lock.json b/package-lock.json index 499f9dd..ed7a586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "ckplayer": "^3.1.2", "dplayer": "^1.27.1", "echarts": "5.6.0", + "marked": "^16.4.0", "naive-ui": "^2.42.0", "naive-ui-editor": "^1.0.6", "pinia": "^3.0.3", @@ -33,6 +34,7 @@ "@types/dplayer": "^1.25.5", "@types/node": "^24.0.15", "@vitejs/plugin-vue": "^6.0.0", + "less": "^4.4.2", "sass-embedded": "^1.93.2", "typescript": "^5.8.3", "vite": "^7.0.0", @@ -2909,6 +2911,20 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-stack-parser-es": { "version": "0.1.5", "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", @@ -3474,6 +3490,34 @@ "@babel/runtime": "^7.12.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", @@ -3760,6 +3804,53 @@ "dev": true, "license": "MIT" }, + "node_modules/less": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/less/-/less-4.4.2.tgz", + "integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less/node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/less/node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", @@ -3834,6 +3925,44 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/marked": { + "version": "16.4.0", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.0.tgz", + "integrity": "sha512-CTPAcRBq57cn3R8n3hwc2REddc28hjR7RzDXQ+lXLmMJYqn20BaI2cGw6QjgZGIgVfp2Wdfw4aMzgNteQ6qJgQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3872,6 +4001,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -4000,6 +4143,24 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", @@ -4123,6 +4284,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", @@ -4172,6 +4343,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pinia": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz", @@ -4268,6 +4450,14 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/quill": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/quill/-/quill-2.0.3.tgz", @@ -4401,6 +4591,14 @@ "tslib": "^2.1.0" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/sass": { "version": "1.93.2", "resolved": "https://registry.npmmirror.com/sass/-/sass-1.93.2.tgz", @@ -4772,6 +4970,14 @@ "node": ">=14.0.0" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/scroll-into-view-if-needed": { "version": "2.2.31", "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", @@ -4918,6 +5124,17 @@ "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==", "license": "MIT" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index a3dc000..6476e53 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "ckplayer": "^3.1.2", "dplayer": "^1.27.1", "echarts": "5.6.0", + "marked": "^16.4.0", "naive-ui": "^2.42.0", "naive-ui-editor": "^1.0.6", "pinia": "^3.0.3", @@ -38,6 +39,7 @@ "@types/dplayer": "^1.25.5", "@types/node": "^24.0.15", "@vitejs/plugin-vue": "^6.0.0", + "less": "^4.4.2", "sass-embedded": "^1.93.2", "typescript": "^5.8.3", "vite": "^7.0.0", diff --git a/src/views/teacher/ai-knowledge-naive-ui/AiKnowledgeBaseList.vue b/src/views/teacher/ai-knowledge-naive-ui/AiKnowledgeBaseList.vue new file mode 100644 index 0000000..6bcfdb8 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/AiKnowledgeBaseList.vue @@ -0,0 +1,540 @@ + + + + + diff --git a/src/views/teacher/ai-knowledge-naive-ui/DEPLOYMENT.md b/src/views/teacher/ai-knowledge-naive-ui/DEPLOYMENT.md new file mode 100644 index 0000000..85f0b2f --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/DEPLOYMENT.md @@ -0,0 +1,314 @@ +# 部署指南 + +## 快速开始 + +### 1. 环境要求 + +- Node.js >= 16.0.0 +- npm >= 8.0.0 或 yarn >= 1.22.0 或 pnpm >= 7.0.0 + +### 2. 安装依赖 + +```bash +# 使用 npm +npm install + +# 使用 yarn +yarn install + +# 使用 pnpm +pnpm install +``` + +### 3. 环境配置 + +创建 `.env` 文件: + +```env +# API基础地址 +VITE_API_BASE_URL=http://localhost:8080/api + +# 上传文件地址 +VITE_UPLOAD_URL=http://localhost:8080/api/sys/common/upload + +# 静态资源地址 +VITE_STATIC_URL=http://localhost:8080/api/sys/common/static +``` + +### 4. 开发模式 + +```bash +npm run dev +``` + +访问 http://localhost:3000 + +### 5. 生产构建 + +```bash +npm run build +``` + +构建产物在 `dist` 目录中。 + +## 集成到现有项目 + +### 1. 作为组件库使用 + +```bash +npm install ai-knowledge-naive-ui +``` + +```vue + + + +``` + +### 2. 直接复制源码 + +将 `ai-knowledge-naive-ui` 目录复制到你的项目中,然后: + +```vue + + + +``` + +## 配置说明 + +### HTTP请求配置 + +修改 `utils/http.ts` 中的配置: + +```typescript +export const http = new HttpClient({ + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', + timeout: 60000, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } +}) +``` + +### 认证配置 + +如果你的API需要认证,修改 `utils/http.ts` 中的请求拦截器: + +```typescript +// 请求拦截器 +this.instance.interceptors.request.use( + (config) => { + // 添加token等认证信息 + const token = localStorage.getItem('token') + // 或者从你的状态管理中获取 + // const token = useAuthStore().token + + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) +``` + +### 路由配置 + +如果你使用Vue Router,可以这样配置路由: + +```typescript +import { createRouter, createWebHistory } from 'vue-router' +import AiKnowledgeBaseList from './ai-knowledge-naive-ui/AiKnowledgeBaseList.vue' + +const routes = [ + { + path: '/knowledge', + name: 'Knowledge', + component: AiKnowledgeBaseList, + meta: { + title: 'AI知识库管理' + } + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router +``` + +## 主题配置 + +### 使用Naive UI主题 + +```vue + + + +``` + +### 自定义主题 + +```vue + + + +``` + +## 权限控制 + +### 路由守卫 + +```typescript +router.beforeEach((to, from, next) => { + // 检查用户是否有访问知识库的权限 + const hasPermission = checkPermission('knowledge:view') + + if (to.path.startsWith('/knowledge') && !hasPermission) { + next('/403') + } else { + next() + } +}) +``` + +### 组件级权限 + +```vue + + + +``` + +## 性能优化 + +### 懒加载 + +```typescript +// 路由懒加载 +const routes = [ + { + path: '/knowledge', + name: 'Knowledge', + component: () => import('./ai-knowledge-naive-ui/AiKnowledgeBaseList.vue') + } +] +``` + +### 组件懒加载 + +```vue + +``` + +## 故障排除 + +### 常见问题 + +1. **API请求失败** + - 检查 `.env` 文件中的API地址配置 + - 确认后端服务是否正常运行 + - 检查网络连接和CORS配置 + +2. **组件样式异常** + - 确认已正确引入Naive UI的样式 + - 检查CSS预处理器配置 + - 确认没有样式冲突 + +3. **TypeScript类型错误** + - 确认已安装所有必要的类型定义包 + - 检查tsconfig.json配置 + - 更新依赖包到最新版本 + +4. **文件上传失败** + - 检查上传接口地址配置 + - 确认文件大小和类型限制 + - 检查服务器上传配置 + +### 调试技巧 + +1. 开启开发者工具的网络面板查看API请求 +2. 使用Vue DevTools检查组件状态 +3. 查看浏览器控制台的错误信息 +4. 使用断点调试JavaScript代码 + +## 更新升级 + +### 版本更新 + +```bash +# 检查可更新的包 +npm outdated + +# 更新依赖 +npm update + +# 更新到最新版本 +npm install ai-knowledge-naive-ui@latest +``` + +### 迁移指南 + +当有重大版本更新时,请查看CHANGELOG.md文件了解变更内容和迁移步骤。 diff --git a/src/views/teacher/ai-knowledge-naive-ui/README.md b/src/views/teacher/ai-knowledge-naive-ui/README.md new file mode 100644 index 0000000..ef83e19 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/README.md @@ -0,0 +1,186 @@ +# AI知识库管理系统 - Naive UI版本 + +这是将原有的Ant Design Vue + JeecgBoot项目中的AI知识库管理功能转换为Naive UI + TypeScript + Vue3版本的实现。 + +## 📁 文件结构 + +``` +ai-knowledge-naive-ui/ +├── AiKnowledgeBaseList.vue # 主页面组件 - 知识库列表 +├── api/ +│ └── knowledge.ts # AI知识库相关API +├── components/ +│ ├── KnowledgeBaseModal.vue # 知识库表单组件 +│ ├── KnowledgeDocListModal.vue # 知识库文档列表组件 +│ ├── KnowledgeDocTextModal.vue # 文档编辑组件 +│ └── TextDescModal.vue # 文本详情组件 +├── types/ +│ └── knowledge.ts # 类型定义 +├── utils/ +│ ├── http.ts # HTTP请求工具 +│ ├── clipboard.ts # 剪贴板工具 +│ ├── file.ts # 文件处理工具 +│ └── common.ts # 通用工具函数 +└── README.md # 说明文档 +``` + +## 🔄 组件转换对照 + +### 原组件 → 新组件 + +| 原组件 | 新组件 | 说明 | +|--------|--------|------| +| `a-card` | `n-card` | 卡片组件 | +| `a-form` | `n-form` | 表单组件 | +| `a-input` | `n-input` | 输入框组件 | +| `a-select` | `n-select` | 选择器组件 | +| `a-button` | `n-button` | 按钮组件 | +| `a-modal` | `n-modal` | 弹窗组件 | +| `a-pagination` | `n-pagination` | 分页组件 | +| `a-tag` | `n-tag` | 标签组件 | +| `a-tooltip` | `n-tooltip` | 提示组件 | +| `a-dropdown` | `n-dropdown` | 下拉菜单组件 | +| `a-upload` | `n-upload` | 上传组件 | +| `a-table` | `n-data-table` | 表格组件 | +| `a-layout` | `n-layout` | 布局组件 | +| `a-menu` | `n-menu` | 菜单组件 | + +## 🚀 使用方法 + +### 1. 安装依赖 + +```bash +npm install naive-ui @vicons/antd marked +# 或 +yarn add naive-ui @vicons/antd marked +# 或 +pnpm add naive-ui @vicons/antd marked +``` + +### 2. 配置Naive UI + +在你的主应用中配置Naive UI: + +```typescript +// main.ts +import { createApp } from 'vue' +import naive from 'naive-ui' +import App from './App.vue' + +const app = createApp(App) +app.use(naive) +app.mount('#app') +``` + +### 3. 使用组件 + +```vue + + + +``` + +## 🔧 配置说明 + +### HTTP请求配置 + +在 `utils/http.ts` 中配置你的API基础地址: + +```typescript +const http = new HttpClient({ + baseURL: 'http://your-api-domain.com/api', // 修改为你的API地址 + timeout: 60000 +}) +``` + +### 环境变量配置 + +创建 `.env` 文件: + +```env +VITE_API_BASE_URL=http://localhost:8080/api +``` + +## 📋 功能特性 + +### 知识库管理 +- ✅ 知识库列表展示(卡片式布局) +- ✅ 创建/编辑知识库 +- ✅ 删除知识库(带确认) +- ✅ 知识库向量化 +- ✅ 搜索和分页 + +### 文档管理 +- ✅ 文档列表展示(表格形式) +- ✅ 支持文本、文件、URL三种文档类型 +- ✅ 文档编辑和删除 +- ✅ 批量操作(删除、向量化) +- ✅ 清空所有文档 +- ✅ 文档搜索 + +### 向量化测试 +- ✅ 向量化命中测试 +- ✅ 测试结果展示 +- ✅ 结果详情查看 + +### 文件上传 +- ✅ 拖拽上传 +- ✅ 文件类型验证 +- ✅ 文件大小限制 +- ✅ 上传进度显示 + +## 🎨 样式特性 + +- 响应式设计,支持多种屏幕尺寸 +- 现代化的卡片式布局 +- 优雅的动画效果 +- 一致的视觉风格 +- 深色/浅色主题支持(Naive UI内置) + +## 🔌 API接口 + +### 知识库相关 +- `GET /airag/knowledge/list` - 获取知识库列表 +- `POST /airag/knowledge/add` - 创建知识库 +- `PUT /airag/knowledge/edit` - 编辑知识库 +- `DELETE /airag/knowledge/delete` - 删除知识库 +- `PUT /airag/knowledge/rebuild` - 知识库向量化 + +### 文档相关 +- `GET /airag/knowledge/doc/list` - 获取文档列表 +- `POST /airag/knowledge/doc/edit` - 创建/编辑文档 +- `DELETE /airag/knowledge/doc/deleteBatch` - 批量删除文档 +- `DELETE /airag/knowledge/doc/deleteAll` - 清空所有文档 +- `PUT /airag/knowledge/doc/rebuild` - 文档向量化 +- `POST /airag/knowledge/embedding/hitTest` - 向量化测试 + +## 🐛 注意事项 + +1. **图标引用**: 使用`@vicons/antd`包中的图标,需要按需引入 +2. **样式覆盖**: 使用`:deep()`选择器来覆盖组件内部样式 +3. **类型导入**: 确保正确导入Naive UI的类型定义 +4. **API地址**: 根据实际情况修改API基础地址 +5. **权限控制**: 根据需要添加路由守卫和权限验证 +6. **文件上传**: 需要配置正确的上传接口地址和认证信息 + +## 📝 待完善功能 + +- [ ] 国际化支持 +- [ ] 主题切换 +- [ ] 更多文件类型支持 +- [ ] 导入导出功能 +- [ ] 知识库模板功能 +- [ ] 实时向量化进度显示 +- [ ] 文档预览功能 + +## 🤝 贡献 + +欢迎提交Issue和Pull Request来改进这个项目! + +## 📄 许可证 + +MIT License diff --git a/src/views/teacher/ai-knowledge-naive-ui/api/knowledge.ts b/src/views/teacher/ai-knowledge-naive-ui/api/knowledge.ts new file mode 100644 index 0000000..c74910e --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/api/knowledge.ts @@ -0,0 +1,193 @@ +import { defHttp } from '../utils/http' +import type { + KnowledgeBase, + KnowledgeDoc, + KnowledgeSearchForm, + KnowledgeDocSearchForm, + EmbeddingHitResult, + RebuildVectorParams +} from '../types/knowledge' + +// API 端点枚举 +enum Api { + // 知识库管理 + list = '/airag/knowledge/list', + save = '/airag/knowledge/add', + delete = '/airag/knowledge/delete', + queryById = '/airag/knowledge/queryById', + edit = '/airag/knowledge/edit', + rebuild = '/airag/knowledge/rebuild', + + // 知识库文档 + knowledgeDocList = '/airag/knowledge/doc/list', + knowledgeEditDoc = '/airag/knowledge/doc/edit', + knowledgeDeleteBatchDoc = '/airag/knowledge/doc/deleteBatch', + knowledgeDeleteAllDoc = '/airag/knowledge/doc/deleteAll', + knowledgeRebuildDoc = '/airag/knowledge/doc/rebuild', + knowledgeEmbeddingHitTest = '/airag/knowledge/embedding/hitTest', +} + +/** + * 查询知识库列表 + * @param params 查询参数 + */ +export const getKnowledgeList = (params: KnowledgeSearchForm & { pageNo?: number; pageSize?: number }) => { + return defHttp.get<{ + records: KnowledgeBase[] + total: number + size: number + current: number + }>({ + url: Api.list, + params + }, { isTransformResponse: false }) +} + +/** + * 根据ID查询知识库详情 + * @param params 查询参数 + */ +export const getKnowledgeById = (params: { id: string }) => { + return defHttp.get({ + url: Api.queryById, + params + }, { isTransformResponse: false }) +} + +/** + * 新增知识库 + * @param params 知识库数据 + */ +export const createKnowledge = (params: Omit) => { + return defHttp.post({ + url: Api.save, + params + }) +} + +/** + * 编辑知识库 + * @param params 知识库数据 + */ +export const updateKnowledge = (params: KnowledgeBase) => { + return defHttp.put({ + url: Api.edit, + params + }) +} + +/** + * 删除知识库 + * @param params 删除参数 + */ +export const deleteKnowledge = (params: { id: string }) => { + return defHttp.delete({ + url: Api.delete, + params + }, { joinParamsToUrl: true }) +} + +/** + * 知识库向量化 + * @param params 向量化参数 + */ +export const rebuildKnowledgeVector = (params: RebuildVectorParams) => { + return defHttp.put({ + url: Api.rebuild, + params, + timeout: 2 * 60 * 1000 + }, { + joinParamsToUrl: true, + isTransformResponse: false + }) +} + +/** + * 查询知识库文档列表 + * @param params 查询参数 + */ +export const getKnowledgeDocList = (params: KnowledgeDocSearchForm & { + knowledgeId: string + pageNo?: number + pageSize?: number +}) => { + return defHttp.get<{ + records: KnowledgeDoc[] + total: number + size: number + current: number + }>({ + url: Api.knowledgeDocList, + params + }, { isTransformResponse: false }) +} + +/** + * 新增/编辑知识库文档 + * @param params 文档数据 + */ +export const saveKnowledgeDoc = (params: Partial) => { + return defHttp.post({ + url: Api.knowledgeEditDoc, + params + }) +} + +/** + * 批量删除文档 + * @param params 删除参数 + */ +export const batchDeleteKnowledgeDocs = (params: { ids: string }) => { + return defHttp.delete({ + url: Api.knowledgeDeleteBatchDoc, + params + }, { joinParamsToUrl: true }) +} + +/** + * 清空所有文档 + * @param knowledgeId 知识库ID + */ +export const deleteAllKnowledgeDocs = (knowledgeId: string) => { + return defHttp.delete({ + url: Api.knowledgeDeleteAllDoc, + params: { knowledgeId } + }, { joinParamsToUrl: true }) +} + +/** + * 文档向量化 + * @param params 向量化参数 + */ +export const rebuildDocVector = (params: { docIds: string }) => { + return defHttp.put({ + url: Api.knowledgeRebuildDoc, + params + }, { joinParamsToUrl: true }) +} + +/** + * 向量化命中测试 + * @param params 测试参数 + */ +export const embeddingHitTest = (params: { + knowledgeId: string + query: string + topK?: number +}) => { + return defHttp.post({ + url: Api.knowledgeEmbeddingHitTest, + params + }) +} + +/** + * 批量查询知识库 + * @param params 查询参数 + */ +export const batchQueryKnowledgeById = (params: { ids: string }) => { + return defHttp.get({ + url: '/airag/knowledge/query/batch/byId', + params + }, { isTransformResponse: false }) +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeBaseModal.vue b/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeBaseModal.vue new file mode 100644 index 0000000..16fc1ad --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeBaseModal.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocListModal.vue b/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocListModal.vue new file mode 100644 index 0000000..3b02978 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocListModal.vue @@ -0,0 +1,775 @@ + + + + + diff --git a/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocTextModal.vue b/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocTextModal.vue new file mode 100644 index 0000000..ed9a267 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/components/KnowledgeDocTextModal.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/src/views/teacher/ai-knowledge-naive-ui/components/TextDescModal.vue b/src/views/teacher/ai-knowledge-naive-ui/components/TextDescModal.vue new file mode 100644 index 0000000..2a0abf1 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/components/TextDescModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/views/teacher/ai-knowledge-naive-ui/example/App.vue b/src/views/teacher/ai-knowledge-naive-ui/example/App.vue new file mode 100644 index 0000000..9e55372 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/example/App.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/views/teacher/ai-knowledge-naive-ui/index.ts b/src/views/teacher/ai-knowledge-naive-ui/index.ts new file mode 100644 index 0000000..207c0e5 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/index.ts @@ -0,0 +1,23 @@ +/** + * AI知识库管理系统 - Naive UI版本 + * 导出所有组件和工具函数 + */ + +// 主要组件 +export { default as AiKnowledgeBaseList } from './AiKnowledgeBaseList.vue' +export { default as KnowledgeBaseModal } from './components/KnowledgeBaseModal.vue' +export { default as KnowledgeDocListModal } from './components/KnowledgeDocListModal.vue' +export { default as KnowledgeDocTextModal } from './components/KnowledgeDocTextModal.vue' +export { default as TextDescModal } from './components/TextDescModal.vue' + +// API接口 +export * from './api/knowledge' + +// 类型定义 +export * from './types/knowledge' + +// 工具函数 +export * from './utils/http' +export * from './utils/clipboard' +export * from './utils/file' +export * from './utils/common' diff --git a/src/views/teacher/ai-knowledge-naive-ui/package.json b/src/views/teacher/ai-knowledge-naive-ui/package.json new file mode 100644 index 0000000..557caed --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/package.json @@ -0,0 +1,58 @@ +{ + "name": "ai-knowledge-naive-ui", + "version": "1.0.0", + "description": "AI知识库管理系统 - Naive UI版本", + "main": "index.ts", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview", + "type-check": "vue-tsc --noEmit" + }, + "keywords": [ + "vue3", + "typescript", + "naive-ui", + "ai", + "knowledge-base", + "vector-database" + ], + "author": "Your Name", + "license": "MIT", + "dependencies": { + "vue": "^3.3.0", + "naive-ui": "^2.35.0", + "@vicons/antd": "^0.12.0", + "axios": "^1.5.0", + "marked": "^9.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@vitejs/plugin-vue": "^4.4.0", + "typescript": "^5.2.0", + "vite": "^4.4.0", + "vue-tsc": "^1.8.0", + "less": "^4.2.0" + }, + "peerDependencies": { + "vue": ">=3.3.0" + }, + "files": [ + "*.vue", + "api/", + "components/", + "types/", + "utils/", + "index.ts", + "README.md" + ], + "repository": { + "type": "git", + "url": "https://github.com/your-username/ai-knowledge-naive-ui.git" + }, + "bugs": { + "url": "https://github.com/your-username/ai-knowledge-naive-ui/issues" + }, + "homepage": "https://github.com/your-username/ai-knowledge-naive-ui#readme" +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/tsconfig.json b/src/views/teacher/ai-knowledge-naive-ui/tsconfig.json new file mode 100644 index 0000000..1231a2b --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/tsconfig.json @@ -0,0 +1,46 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + }, + + /* Vue specific */ + "types": ["vite/client", "node"] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.vue" + ], + "exclude": [ + "node_modules", + "dist" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/tsconfig.node.json b/src/views/teacher/ai-knowledge-naive-ui/tsconfig.node.json new file mode 100644 index 0000000..b940375 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/types/knowledge.ts b/src/views/teacher/ai-knowledge-naive-ui/types/knowledge.ts new file mode 100644 index 0000000..8083fb8 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/types/knowledge.ts @@ -0,0 +1,174 @@ +/** + * AI知识库相关类型定义 + */ + +// 知识库基础信息 +export interface KnowledgeBase { + id: string + name: string + descr?: string + embedId: string + embedName?: string + status?: 'active' | 'inactive' + createBy?: string + createBy_dictText?: string + createTime?: string + updateBy?: string + updateTime?: string + docCount?: number + vectorCount?: number +} + +// 知识库搜索表单 +export interface KnowledgeSearchForm { + name?: string + status?: string + createBy?: string +} + +// 知识库文档 +export interface KnowledgeDoc { + id: string + knowledgeId: string + title: string + content?: string + type: 'text' | 'file' | 'url' + source?: string + metadata?: string + filePath?: string + status?: 'processing' | 'completed' | 'failed' + createBy?: string + createBy_dictText?: string + createTime?: string + updateBy?: string + updateTime?: string + vectorStatus?: 'pending' | 'processing' | 'completed' | 'failed' + __checked?: boolean // 用于表格选择 +} + +// 知识库文档搜索表单 +export interface KnowledgeDocSearchForm { + title?: string + type?: string + status?: string +} + +// 向量化命中测试结果 +export interface EmbeddingHitResult { + id: string + content: string + source: string + score: number + metadata?: any +} + +// 分页参数 +export interface Pagination { + page: number + pageSize: number + total: number +} + +// 表单规则 +export interface FormRules { + [key: string]: Array<{ + required?: boolean + message: string + trigger?: string | string[] + min?: number + max?: number + pattern?: RegExp + validator?: (rule: any, value: any) => boolean | Promise + }> +} + +// 知识库表单数据 +export interface KnowledgeFormData { + id?: string + name: string + descr?: string + embedId: string +} + +// 知识库文档表单数据 +export interface KnowledgeDocFormData { + id?: string + knowledgeId?: string + title: string + content?: string + type: 'text' | 'file' | 'url' + filePath?: string + metadata?: string +} + +// 文件上传响应 +export interface UploadResponse { + success: boolean + message: string + result: { + filename: string + url: string + size: number + } +} + +// 向量模型选项 +export interface EmbedModelOption { + label: string + value: string + disabled?: boolean +} + +// 菜单项 +export interface MenuItem { + key: string + label: string + icon?: string + disabled?: boolean +} + +// 表格列配置 +export interface TableColumn { + key: string + title: string + width?: number + align?: 'left' | 'center' | 'right' + ellipsis?: boolean + render?: (row: any) => any +} + +// 操作按钮配置 +export interface ActionButton { + label: string + type?: 'primary' | 'info' | 'success' | 'warning' | 'error' + icon?: string + disabled?: boolean + loading?: boolean + onClick: () => void +} + +// 知识库统计信息 +export interface KnowledgeStats { + totalKnowledge: number + totalDocs: number + totalVectors: number + processingDocs: number +} + +// 批量操作参数 +export interface BatchOperationParams { + ids: string[] + operation: 'delete' | 'rebuild' | 'enable' | 'disable' +} + +// 重建向量参数 +export interface RebuildVectorParams { + knowIds?: string + docIds?: string[] +} + +// 清空文档参数 +export interface DeleteAllDocsParams { + knowledgeId: string + confirm: boolean +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/utils/clipboard.ts b/src/views/teacher/ai-knowledge-naive-ui/utils/clipboard.ts new file mode 100644 index 0000000..45dc108 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/utils/clipboard.ts @@ -0,0 +1,48 @@ +/** + * 复制文本到剪贴板 + * @param text 要复制的文本 + * @returns Promise 是否复制成功 + */ +export async function copyToClipboard(text: string): Promise { + try { + // 优先使用现代的 Clipboard API + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text) + return true + } + + // 降级方案:使用 document.execCommand + const textArea = document.createElement('textarea') + textArea.value = text + textArea.style.position = 'fixed' + textArea.style.left = '-999999px' + textArea.style.top = '-999999px' + document.body.appendChild(textArea) + textArea.focus() + textArea.select() + + const success = document.execCommand('copy') + document.body.removeChild(textArea) + + return success + } catch (error) { + console.error('复制到剪贴板失败:', error) + return false + } +} + +/** + * 从剪贴板读取文本 + * @returns Promise 剪贴板中的文本 + */ +export async function readFromClipboard(): Promise { + try { + if (navigator.clipboard && window.isSecureContext) { + return await navigator.clipboard.readText() + } + throw new Error('Clipboard API not supported') + } catch (error) { + console.error('从剪贴板读取失败:', error) + return '' + } +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/utils/common.ts b/src/views/teacher/ai-knowledge-naive-ui/utils/common.ts new file mode 100644 index 0000000..830b941 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/utils/common.ts @@ -0,0 +1,173 @@ +/** + * 通用工具函数 + */ + +/** + * 防抖函数 + * @param func 要防抖的函数 + * @param wait 等待时间(毫秒) + * @returns 防抖后的函数 + */ +export function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null + + return function (this: any, ...args: Parameters) { + if (timeout) { + clearTimeout(timeout) + } + timeout = setTimeout(() => func.apply(this, args), wait) + } +} + +/** + * 节流函数 + * @param func 要节流的函数 + * @param wait 等待时间(毫秒) + * @returns 节流后的函数 + */ +export function throttle any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null + let previous = 0 + + return function (this: any, ...args: Parameters) { + const now = Date.now() + const remaining = wait - (now - previous) + const context = this + + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout) + timeout = null + } + previous = now + func.apply(context, args) + } else if (!timeout) { + timeout = setTimeout(() => { + previous = Date.now() + timeout = null + func.apply(context, args) + }, remaining) + } + } +} + +/** + * 深拷贝对象 + * @param obj 要拷贝的对象 + * @returns 拷贝后的对象 + */ +export function deepClone(obj: T): T { + if (obj === null || typeof obj !== 'object') { + return obj + } + + if (obj instanceof Date) { + return new Date(obj.getTime()) as T + } + + if (obj instanceof Array) { + return obj.map(item => deepClone(item)) as T + } + + if (typeof obj === 'object') { + const clonedObj = {} as T + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + clonedObj[key] = deepClone(obj[key]) + } + } + return clonedObj + } + + return obj +} + +/** + * 生成唯一ID + * @returns 唯一ID字符串 + */ +export function generateId(): string { + return Math.random().toString(36).substr(2, 9) + Date.now().toString(36) +} + +/** + * 格式化日期 + * @param date 日期对象或时间戳 + * @param format 格式字符串,默认为 'YYYY-MM-DD HH:mm:ss' + * @returns 格式化后的日期字符串 + */ +export function formatDate(date: Date | number | string, format = 'YYYY-MM-DD HH:mm:ss'): string { + const d = new Date(date) + + if (isNaN(d.getTime())) { + return '' + } + + const year = d.getFullYear() + const month = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + const hours = String(d.getHours()).padStart(2, '0') + const minutes = String(d.getMinutes()).padStart(2, '0') + const seconds = String(d.getSeconds()).padStart(2, '0') + + return format + .replace('YYYY', year.toString()) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('mm', minutes) + .replace('ss', seconds) +} + +/** + * 检查是否为空值 + * @param value 要检查的值 + * @returns 是否为空 + */ +export function isEmpty(value: any): boolean { + if (value === null || value === undefined) { + return true + } + + if (typeof value === 'string') { + return value.trim() === '' + } + + if (Array.isArray(value)) { + return value.length === 0 + } + + if (typeof value === 'object') { + return Object.keys(value).length === 0 + } + + return false +} + +/** + * 替换图片宽度 + * @param content 内容字符串 + * @returns 替换后的内容 + */ +export function replaceImageWith(content: string): string { + return content.replace(/]*?)style="([^"]*?)"([^>]*?)>/g, (_match, before, style, after) => { + const newStyle = style.replace(/width:\s*\d+px/g, 'width: 100%') + return `` + }) +} + +/** + * 替换域名URL + * @param content 内容字符串 + * @returns 替换后的内容 + */ +export function replaceDomainUrl(content: string): string { + const baseUrl = import.meta.env.VITE_API_BASE_URL || '/api' + return content.replace(/src="\/sys\/common\/static\//g, `src="${baseUrl}/sys/common/static/`) +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/utils/file.ts b/src/views/teacher/ai-knowledge-naive-ui/utils/file.ts new file mode 100644 index 0000000..894755e --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/utils/file.ts @@ -0,0 +1,128 @@ +/** + * 文件相关工具函数 + */ + +/** + * 获取文件访问URL + * @param filePath 文件路径 + * @returns 完整的文件访问URL + */ +export function getFileAccessHttpUrl(filePath: string): string { + if (!filePath) return '' + + // 如果已经是完整URL,直接返回 + if (filePath.startsWith('http://') || filePath.startsWith('https://')) { + return filePath + } + + // 拼接API基础路径 + const baseUrl = import.meta.env.VITE_API_BASE_URL || '/api' + return `${baseUrl}/sys/common/static/${filePath}` +} + +/** + * 获取请求头信息 + * @returns 包含认证信息的请求头 + */ +export function getHeaders(): Record { + const token = localStorage.getItem('token') + return { + 'Content-Type': 'application/json', + ...(token && { Authorization: `Bearer ${token}` }) + } +} + +/** + * 文件大小格式化 + * @param bytes 字节数 + * @returns 格式化后的文件大小字符串 + */ +export function formatFileSize(bytes: number): string { + if (bytes === 0) return '0 B' + + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] +} + +/** + * 获取文件扩展名 + * @param filename 文件名 + * @returns 文件扩展名(不包含点) + */ +export function getFileExtension(filename: string): string { + return filename.slice((filename.lastIndexOf('.') - 1 >>> 0) + 2) +} + +/** + * 检查文件类型是否为图片 + * @param filename 文件名或文件类型 + * @returns 是否为图片类型 + */ +export function isImageFile(filename: string): boolean { + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'] + const extension = getFileExtension(filename).toLowerCase() + return imageExtensions.includes(extension) +} + +/** + * 检查文件类型是否为文档 + * @param filename 文件名或文件类型 + * @returns 是否为文档类型 + */ +export function isDocumentFile(filename: string): boolean { + const docExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md'] + const extension = getFileExtension(filename).toLowerCase() + return docExtensions.includes(extension) +} + +/** + * 文件下载 + * @param url 下载链接 + * @param filename 文件名 + */ +export function downloadFile(url: string, filename?: string): void { + const link = document.createElement('a') + link.href = url + if (filename) { + link.download = filename + } + link.target = '_blank' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) +} + +/** + * 将文件转换为Base64 + * @param file 文件对象 + * @returns Promise Base64字符串 + */ +export function fileToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => resolve(reader.result as string) + reader.onerror = error => reject(error) + }) +} + +/** + * Base64转换为Blob + * @param base64 Base64字符串 + * @param mimeType MIME类型 + * @returns Blob对象 + */ +export function base64ToBlob(base64: string, mimeType: string): Blob { + const byteCharacters = atob(base64.split(',')[1]) + const byteNumbers = new Array(byteCharacters.length) + + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i) + } + + const byteArray = new Uint8Array(byteNumbers) + return new Blob([byteArray], { type: mimeType }) +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/utils/http.ts b/src/views/teacher/ai-knowledge-naive-ui/utils/http.ts new file mode 100644 index 0000000..d66aaee --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/utils/http.ts @@ -0,0 +1,203 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' + +export interface HttpResponse { + success: boolean + result: T + message: string + code: number + timestamp: number +} + +export interface RequestConfig extends AxiosRequestConfig { + isTransformResponse?: boolean + joinParamsToUrl?: boolean +} + +export class HttpClient { + private instance: AxiosInstance + + constructor(config?: AxiosRequestConfig) { + this.instance = axios.create({ + baseURL: '/jeecgboot', + timeout: 60000, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + ...config + }) + + this.setupInterceptors() + } + + private setupInterceptors() { + // 请求拦截器 + this.instance.interceptors.request.use( + (config) => { + // 添加token等认证信息(使用项目标准的 X-Access-Token) + const token = localStorage.getItem('X-Access-Token') || sessionStorage.getItem('X-Access-Token') + if (token) { + config.headers = config.headers || {} + config.headers['X-Access-Token'] = token + } + + // 添加租户ID + const tenantId = localStorage.getItem('TENANT_ID') || '0' + if (tenantId) { + config.headers = config.headers || {} + config.headers['X-Tenant-Id'] = tenantId + } + + return config + }, + (error) => { + return Promise.reject(error) + } + ) + + // 响应拦截器 + this.instance.interceptors.response.use( + (response: AxiosResponse) => { + const { data } = response + + // 如果是文件下载等特殊情况,直接返回 + if (response.config.responseType === 'blob') { + return response + } + + // 检查业务状态码 + if (data.success === false) { + throw new Error(data.message || '请求失败') + } + + return response + }, + (error) => { + // 处理HTTP错误状态码 + if (error.response) { + const { status, data } = error.response + switch (status) { + case 401: + // 未授权,清除token并跳转到登录页 + localStorage.removeItem('X-Access-Token') + sessionStorage.removeItem('X-Access-Token') + if (window.location.pathname !== '/login') { + window.location.href = '/login' + } + break + case 403: + throw new Error('没有权限访问该资源') + case 404: + throw new Error('请求的资源不存在') + case 500: + throw new Error('服务器内部错误') + default: + throw new Error(data?.message || `请求失败 (${status})`) + } + } else if (error.request) { + throw new Error('网络连接失败,请检查网络设置') + } else { + throw new Error(error.message || '请求配置错误') + } + return Promise.reject(error) + } + ) + } + + // GET请求 + get(config: RequestConfig, options?: { isTransformResponse?: boolean; joinParamsToUrl?: boolean }): Promise> { + const mergedConfig = { ...config, ...options } + const { joinParamsToUrl, isTransformResponse = true, ...axiosConfig } = mergedConfig + + if (joinParamsToUrl && mergedConfig.params) { + const params = new URLSearchParams(mergedConfig.params).toString() + axiosConfig.url = `${axiosConfig.url}?${params}` + delete axiosConfig.params + } + + return this.instance.get(axiosConfig.url!, axiosConfig).then(response => { + return isTransformResponse ? response.data : response + }) + } + + // POST请求 + post(config: RequestConfig, options?: { isTransformResponse?: boolean }): Promise> { + const mergedConfig = { ...config, ...options } + const { isTransformResponse = true, ...axiosConfig } = mergedConfig + return this.instance.post(axiosConfig.url!, axiosConfig.params || axiosConfig.data, axiosConfig).then(response => { + return isTransformResponse ? response.data : response + }) + } + + // PUT请求 + put(config: RequestConfig, options?: { isTransformResponse?: boolean; joinParamsToUrl?: boolean }): Promise> { + const mergedConfig = { ...config, ...options } + const { joinParamsToUrl, isTransformResponse = true, ...axiosConfig } = mergedConfig + + if (joinParamsToUrl && mergedConfig.params) { + const params = new URLSearchParams(mergedConfig.params).toString() + axiosConfig.url = `${axiosConfig.url}?${params}` + delete axiosConfig.params + } + + return this.instance.put(axiosConfig.url!, axiosConfig.params || axiosConfig.data, axiosConfig).then(response => { + return isTransformResponse ? response.data : response + }) + } + + // DELETE请求 + delete(config: RequestConfig, options?: { isTransformResponse?: boolean; joinParamsToUrl?: boolean }): Promise> { + const mergedConfig = { ...config, ...options } + const { joinParamsToUrl, isTransformResponse = true, ...axiosConfig } = mergedConfig + + if (joinParamsToUrl && mergedConfig.params) { + const params = new URLSearchParams(mergedConfig.params).toString() + axiosConfig.url = `${axiosConfig.url}?${params}` + delete axiosConfig.params + } + + return this.instance.delete(axiosConfig.url!, axiosConfig).then(response => { + return isTransformResponse ? response.data : response + }) + } + + // 文件上传 + upload(url: string, formData: FormData, config?: AxiosRequestConfig): Promise> { + return this.instance.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + ...config + }).then(response => response.data) + } + + // 文件下载 + download(url: string, params?: any, filename?: string): Promise { + return this.instance.get(url, { + params, + responseType: 'blob' + }).then(response => { + const blob = new Blob([response.data]) + const downloadUrl = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = downloadUrl + link.download = filename || 'download' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(downloadUrl) + }) + } +} + +// 创建默认实例 +export const http = new HttpClient() + +// 导出默认实例的方法 +export const defHttp = { + get: http.get.bind(http), + post: http.post.bind(http), + put: http.put.bind(http), + delete: http.delete.bind(http), + upload: http.upload.bind(http), + download: http.download.bind(http) +} diff --git a/src/views/teacher/ai-knowledge-naive-ui/vite.config.ts b/src/views/teacher/ai-knowledge-naive-ui/vite.config.ts new file mode 100644 index 0000000..59a8b29 --- /dev/null +++ b/src/views/teacher/ai-knowledge-naive-ui/vite.config.ts @@ -0,0 +1,50 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, '.'), + }, + }, + css: { + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3000, + open: true, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '/api'), + }, + }, + }, + build: { + lib: { + entry: resolve(__dirname, 'index.ts'), + name: 'AiKnowledgeNaiveUI', + fileName: (format) => `ai-knowledge-naive-ui.${format}.js`, + }, + rollupOptions: { + external: ['vue', 'naive-ui', '@vicons/antd', 'axios', 'marked'], + output: { + globals: { + vue: 'Vue', + 'naive-ui': 'naive', + '@vicons/antd': 'ViconsAntd', + axios: 'axios', + marked: 'marked', + }, + }, + }, + }, +})