Merge branch 'dev' of http://110.42.96.64:19890/GoCo/OL-LearnPlatform-Frontend into dev
This commit is contained in:
commit
00ae37c216
@ -15,6 +15,10 @@ export { default as ExamApi } from './modules/exam'
|
||||
export { ChatApi } from './modules/chat'
|
||||
export { default as MessageApi } from './modules/message'
|
||||
export type { MessageItem, BackendMessageItem, SystemMessage } from './modules/message'
|
||||
export { default as MenuApi } from './modules/menu'
|
||||
export type { MenuItem } from './modules/menu'
|
||||
export { SystemApi } from './modules/system'
|
||||
export type { SystemSettings, DictItem } from './modules/system'
|
||||
|
||||
// API 基础配置
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/jeecgboot'
|
||||
@ -247,6 +251,12 @@ export const API_ENDPOINTS = {
|
||||
BATCH_DELETE: '/aiol/message/likes/batch-delete',
|
||||
},
|
||||
|
||||
// 菜单相关
|
||||
MENU: {
|
||||
INDEX_MENUS: '/aiol/aiolMenu/getIndexMenus',
|
||||
STUDENT_MENUS: '/aiol/aiolMenu/getStudentMenus',
|
||||
},
|
||||
|
||||
// 资源相关
|
||||
RESOURCES: {
|
||||
DOWNLOAD: '/resources/:id/download',
|
||||
|
@ -9,6 +9,16 @@ export interface SystemSettings {
|
||||
maintenanceEndTime?: string
|
||||
}
|
||||
|
||||
// 字典项接口
|
||||
export interface DictItem {
|
||||
value: string
|
||||
text: string
|
||||
color: string | null
|
||||
jsonObject: any | null
|
||||
label: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export const SystemApi = {
|
||||
// 获取系统设置
|
||||
getSystemSettings(): Promise<ApiResponse<SystemSettings>> {
|
||||
@ -28,5 +38,20 @@ export const SystemApi = {
|
||||
// 切换网站状态
|
||||
toggleSiteStatus(enabled: boolean): Promise<ApiResponse<{ enabled: boolean }>> {
|
||||
return request.post('/aiol/system/toggle', { enabled })
|
||||
},
|
||||
|
||||
// 获取字典项
|
||||
getDictItems(dictCode: string, params?: string): Promise<ApiResponse<DictItem[]>> {
|
||||
const timestamp = Date.now()
|
||||
const url = params
|
||||
? `/sys/dict/getDictItems/${dictCode},${params}?_t=${timestamp}`
|
||||
: `/sys/dict/getDictItems/${dictCode}?_t=${timestamp}`
|
||||
return request.get(url)
|
||||
},
|
||||
|
||||
// 获取AI模型字典项(LLM类型)
|
||||
getAiModelDict(): Promise<ApiResponse<DictItem[]>> {
|
||||
const timestamp = Date.now()
|
||||
return request.get(`/sys/dict/getDictItems/airag_model%20where%20model_type%20=%20'LLM',name,id?_t=${timestamp}`)
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,10 @@ request.interceptors.request.use(
|
||||
}
|
||||
|
||||
// 添加请求时间戳
|
||||
config.headers['X-Request-Time'] = Date.now().toString()
|
||||
const timestamp = Date.now().toString()
|
||||
config.headers['X-Request-Time'] = timestamp
|
||||
config.headers['timestamp'] = timestamp
|
||||
config.headers['X-Timestamp'] = timestamp
|
||||
|
||||
// 开发环境下打印请求信息(已禁用)
|
||||
// if (import.meta.env.DEV) {
|
||||
|
@ -11,10 +11,10 @@
|
||||
<img class="thumbnail_10" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng36400ca1db866102585133601e1ce755c391054588a4f836a964e5d7ff91cb64" />
|
||||
<span class="text-group_7">积分不足,需消耗29智点</span>
|
||||
</div>
|
||||
<div class="box_9 flex-row">
|
||||
<div class="image-text_6 flex-row justify-between">
|
||||
<img class="thumbnail_11" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_8">立即体验</span>
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,10 +11,10 @@
|
||||
<img class="thumbnail_12" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng36400ca1db866102585133601e1ce755c391054588a4f836a964e5d7ff91cb64" />
|
||||
<span class="text-group_10">积分不足,需消耗29智点</span>
|
||||
</div>
|
||||
<div class="group_10 flex-row">
|
||||
<div class="image-text_8 flex-row justify-between">
|
||||
<img class="thumbnail_13" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_11">立即体验</span>
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
import { generateRoutesFromMenus } from '@/utils/routeUtils'
|
||||
|
||||
// ========== 前台页面组件 ==========
|
||||
import Home from '@/views/Home.vue'
|
||||
@ -414,6 +416,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/teacher/airag/ocr/AiOcrList.vue'),
|
||||
meta: { title: 'OCR识别' }
|
||||
},
|
||||
{
|
||||
path: 'ai/app',
|
||||
name: 'TeacherAiAppList',
|
||||
component: () => import('@/views/Ai/AiAppList-NaiveUI.vue'),
|
||||
meta: { title: 'AI应用管理' }
|
||||
},
|
||||
{
|
||||
path: 'student-management',
|
||||
name: 'StudentManagement',
|
||||
@ -790,6 +798,18 @@ const routes: RouteRecordRaw[] = [
|
||||
component: LocalVideoDemo,
|
||||
meta: { title: '本地视频播放演示' }
|
||||
},
|
||||
{
|
||||
path: '/menu-test',
|
||||
name: 'MenuTest',
|
||||
component: () => import('@/views/MenuTest.vue'),
|
||||
meta: { title: '菜单测试' }
|
||||
},
|
||||
{
|
||||
path: '/ai-model-test',
|
||||
name: 'AiModelTest',
|
||||
component: () => import('@/views/AiModelTest.vue'),
|
||||
meta: { title: 'AI模型测试' }
|
||||
},
|
||||
|
||||
// 网站维护页面
|
||||
{
|
||||
@ -848,10 +868,47 @@ const router = createRouter({
|
||||
}
|
||||
})
|
||||
|
||||
// ========== 动态路由加载 ==========
|
||||
let isMenuLoaded = false
|
||||
|
||||
/**
|
||||
* 加载动态菜单路由
|
||||
*/
|
||||
async function loadMenuRoutes() {
|
||||
if (isMenuLoaded) return
|
||||
|
||||
try {
|
||||
const menuStore = useMenuStore()
|
||||
await menuStore.fetchIndexMenus()
|
||||
|
||||
// 生成动态路由
|
||||
const dynamicRoutes = generateRoutesFromMenus(menuStore.indexMenus)
|
||||
|
||||
// 添加动态路由到路由器
|
||||
dynamicRoutes.forEach(route => {
|
||||
// 检查路由是否已存在,避免重复添加
|
||||
if (!router.hasRoute(route.name as string)) {
|
||||
router.addRoute(route)
|
||||
console.log(`✅ 添加动态路由: ${route.path} -> ${String(route.name)}`)
|
||||
}
|
||||
})
|
||||
|
||||
isMenuLoaded = true
|
||||
console.log('✅ 动态菜单路由加载完成')
|
||||
} catch (error) {
|
||||
console.error('❌ 加载动态菜单路由失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 路由守卫 ==========
|
||||
import { maintenanceGuard } from '@/utils/maintenanceGuard'
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// 加载动态菜单路由(仅在首次访问时)
|
||||
if (!isMenuLoaded) {
|
||||
await loadMenuRoutes()
|
||||
}
|
||||
|
||||
// 设置页面标题
|
||||
if (to.meta.title) {
|
||||
document.title = `${to.meta.title} - 在线学习平台`
|
||||
|
234
src/views/Ai.vue
234
src/views/Ai.vue
@ -8,7 +8,8 @@
|
||||
</div>
|
||||
<div class="group_3 flex-row">
|
||||
<span class="text_15">实验方向</span>
|
||||
<span class="text_16">最新</span> <span class="text_17">最热</span>
|
||||
<span class="text_16" :class="{ active: sortType === 'latest' }" @click="setSortType('latest')">最新</span>
|
||||
<span class="text_17" :class="{ active: sortType === 'hot' }" @click="setSortType('hot')">最热</span>
|
||||
<span class="text_18">难度等级</span>
|
||||
<img class="thumbnail_4" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng20bb8df2d25963d2f9a15938bde5268726a9725a399eace8e8b30fa6f6fc6ae0" />
|
||||
@ -19,26 +20,26 @@
|
||||
<div class="group_5 flex-row">
|
||||
<img class="thumbnail_5" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng221d12611b979e5a2255fc34da1f37f982d562e7db29041e6d42e9528e14f249" />
|
||||
<span class="text_19">经典技术</span>
|
||||
<span class="text_20">手势分类</span>
|
||||
<span class="text_19" @click.stop="setCategory('经典技术')" :class="{ active: selectedCategory === '经典技术' }">经典技术</span>
|
||||
<span class="text_20" @click.stop="setCategory('手势分类')" :class="{ active: selectedCategory === '手势分类' }">手势分类</span>
|
||||
</div>
|
||||
<div class="group_6 flex-row">
|
||||
<div class="box_5 flex-col">
|
||||
<img class="image_7" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="image-text_1 flex-row justify-between">
|
||||
<div class="image-text_1 flex-row justify-between" @click.stop="setCategory('在线训练')" :class="{ active: selectedCategory === '在线训练' }">
|
||||
<img class="thumbnail_6" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPngd4359394ad3231a061a6c177963eff548f9dfad49670e0eeb1ea11aa3ac877c2" />
|
||||
<span class="text-group_1">在线训练</span>
|
||||
</div>
|
||||
<img class="image_8" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="image-text_2 flex-row justify-between">
|
||||
<div class="image-text_2 flex-row justify-between" @click.stop="setCategory('神经网络')" :class="{ active: selectedCategory === '神经网络' }">
|
||||
<img class="thumbnail_7" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng9c352004401a0cd1d0034f83919069ac4f46afee4c7472f36df34e0953cca68d" />
|
||||
<span class="text-group_2">神经网络</span>
|
||||
</div>
|
||||
<img class="image_9" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="box_6 flex-row">
|
||||
<div class="image-text_3 flex-row justify-between">
|
||||
<div class="image-text_3 flex-row justify-between" @click.stop="setCategory('特征提取')" :class="{ active: selectedCategory === '特征提取' }">
|
||||
<img class="thumbnail_8" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng3eb646bd16341266f70c97c1b362075b4882c97778d0cce8b71dd3427cd2033c" />
|
||||
<span class="text-group_3">特征提取</span>
|
||||
@ -51,7 +52,7 @@
|
||||
<ThirdProject />
|
||||
</div>
|
||||
<div class="group_11 flex-row justify-between">
|
||||
<div class="image-text_9 flex-row justify-between">
|
||||
<div class="image-text_9 flex-row justify-between" @click.stop="setCategory('原理解释')" :class="{ active: selectedCategory === '原理解释' }">
|
||||
<img class="thumbnail_14" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPngae5cbb0288d044b7940a7e4f58e90a79b376525c7074fe6081d2eb80f3b66af2" />
|
||||
<span class="text-group_12">原理解释</span>
|
||||
@ -61,25 +62,25 @@
|
||||
<img class="image_11" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="group_12 flex-row">
|
||||
<div class="group_13 flex-col justify-between">
|
||||
<div class="image-text_10 flex-row justify-between">
|
||||
<div class="image-text_10 flex-row justify-between" @click.stop="setCategory('大语言模型')" :class="{ active: selectedCategory === '大语言模型' }">
|
||||
<img class="thumbnail_15" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPngd3627550c0a611773c768561b0cc502ab1163b6e3b146e9488302ef4b7251277" />
|
||||
<span class="text-group_13">大语言模型</span>
|
||||
</div>
|
||||
<img class="image_12" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="image-text_11 flex-row justify-between">
|
||||
<div class="image-text_11 flex-row justify-between" @click.stop="setCategory('声音识别')" :class="{ active: selectedCategory === '声音识别' }">
|
||||
<img class="thumbnail_16" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPngcc4df0afc94cd3f9f28423107ee9405bf78e0b8c065738718a8de09e514b3d9c" />
|
||||
<span class="text-group_14">声音识别</span>
|
||||
</div>
|
||||
<img class="image_13" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="image-text_12 flex-row justify-between">
|
||||
<div class="image-text_12 flex-row justify-between" @click.stop="setCategory('图像识别')" :class="{ active: selectedCategory === '图像识别' }">
|
||||
<img class="thumbnail_17" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng9c2b537e7b3537b38d16c6bf24dccbbbcc816a69760c86cc95c6c5075ee387c9" />
|
||||
<span class="text-group_15">图像识别</span>
|
||||
</div>
|
||||
<img class="image_14" referrerpolicy="no-referrer" src="/images/ai/55.jpg" />
|
||||
<div class="image-text_13 flex-row justify-between">
|
||||
<div class="image-text_13 flex-row justify-between" @click.stop="setCategory('生成式AI')" :class="{ active: selectedCategory === '生成式AI' }">
|
||||
<img class="thumbnail_18" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng85eb232fb28be7bf813c024779fa564ca25755fce1f50bfcde00ed68af41ed04" />
|
||||
<span class="text-group_16">生成式AI</span>
|
||||
@ -96,9 +97,11 @@
|
||||
<div class="box_12 flex-row justify-between">
|
||||
<span class="text_34">需消耗20智点</span>
|
||||
<div class="group_19 flex-row">
|
||||
<div class="image-text_15 flex-row justify-between" @click="goToExperience(0)">
|
||||
<img class="thumbnail_20" referrerpolicy="no-referrer" src="/images/ai/33.jpg" />
|
||||
<span class="text-group_20 " >立即体验</span>
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between" @click.stop="goToExperience(0)">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer" src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -113,10 +116,12 @@
|
||||
<div class="box_14 flex-row justify-between">
|
||||
<span class="text_37">还剩3次体验机会</span>
|
||||
<div class="box_15 flex-row">
|
||||
<div class="image-text_16 flex-row justify-between" @click="goToExperience(1)">
|
||||
<img class="thumbnail_21" referrerpolicy="no-referrer"
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between" @click.stop="goToExperience(1)">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_22">立即体验</span>
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -158,10 +163,12 @@
|
||||
<span class="text-group_27">积分不足,需消耗29智点</span>
|
||||
</div>
|
||||
<div class="group_24 flex-row">
|
||||
<div class="image-text_21 flex-row justify-between" @click="goToExperience(2)">
|
||||
<img class="thumbnail_26" referrerpolicy="no-referrer"
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between" @click.stop="goToExperience(2)">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_28">立即体验</span>
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -179,11 +186,11 @@
|
||||
src="https://lanhu-oss-proxy-lanhuapp.com/SketchPng36400ca1db866102585133601e1ce755c391054588a4f836a964e5d7ff91cb64" />
|
||||
<span class="text-group_30">积分不足,需消耗29智点</span>
|
||||
</div>
|
||||
<div class="box_16 flex-row">
|
||||
<div class="image-text_23 flex-row justify-between">
|
||||
<img class="thumbnail_28" referrerpolicy="no-referrer"
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between" @click.stop="goToExperience(3)">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_31">立即体验</span>
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -201,11 +208,11 @@
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng36400ca1db866102585133601e1ce755c391054588a4f836a964e5d7ff91cb64" />
|
||||
<span class="text-group_33">积分不足,需消耗29智点</span>
|
||||
</div>
|
||||
<div class="box_17 flex-row">
|
||||
<div class="image-text_25 flex-row justify-between">
|
||||
<img class="thumbnail_30" referrerpolicy="no-referrer"
|
||||
<div class="group_9 flex-row">
|
||||
<div class="image-text_4 flex-row justify-between" @click.stop="goToExperience(4)">
|
||||
<img class="thumbnail_9" referrerpolicy="no-referrer"
|
||||
src="https://lanhu-oss-proxy.lanhuapp.com/SketchPng8c408105a9fd19d2ed517d26c3972819e5802316449362c24f8fa8609583f1cf" />
|
||||
<span class="text-group_34">立即体验</span>
|
||||
<span class="text-group_5">立即体验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -250,13 +257,93 @@ const ICON_MAP: Record<string, string> = {
|
||||
}
|
||||
|
||||
const selectedTitle = ref<string>('')
|
||||
const sortType = ref<'latest' | 'hot'>('hot') // 默认选择"最热"
|
||||
const selectedCategory = ref<string>('') // 当前选择的分类
|
||||
|
||||
// 设置排序类型
|
||||
const setSortType = (type: 'latest' | 'hot') => {
|
||||
sortType.value = type
|
||||
filterAiApps()
|
||||
}
|
||||
|
||||
// 设置分类
|
||||
const setCategory = (category: string) => {
|
||||
selectedCategory.value = selectedCategory.value === category ? '' : category
|
||||
filterAiApps()
|
||||
}
|
||||
|
||||
// 筛选AI应用
|
||||
const filterAiApps = () => {
|
||||
// 这里可以根据sortType和selectedCategory来筛选和排序右侧的AI应用
|
||||
console.log('当前排序:', sortType.value)
|
||||
console.log('当前分类:', selectedCategory.value)
|
||||
|
||||
// 可以在这里添加实际的筛选逻辑
|
||||
// 比如隐藏/显示不同的AI应用卡片
|
||||
updateVisibleApps()
|
||||
}
|
||||
|
||||
// 更新可见的应用
|
||||
const updateVisibleApps = () => {
|
||||
// 获取所有AI应用卡片(包括组件)
|
||||
const appCards = document.querySelectorAll('.group_18, .group_20, .group_23, .group_25, .group_26, .box_7, .box_8, .box_10')
|
||||
|
||||
appCards.forEach((card, index) => {
|
||||
const cardElement = card as HTMLElement
|
||||
let shouldShow = true
|
||||
|
||||
// 根据分类筛选
|
||||
if (selectedCategory.value) {
|
||||
// 根据实际的应用分类来判断
|
||||
switch (selectedCategory.value) {
|
||||
case '经典技术':
|
||||
shouldShow = index < 3 // 前三个应用属于经典技术
|
||||
break
|
||||
case '手势分类':
|
||||
shouldShow = index === 0 || index === 3 // 手势相关应用
|
||||
break
|
||||
case '在线训练':
|
||||
shouldShow = index === 5 || index === 6 // 训练相关应用(FirstProject, SecondProject)
|
||||
break
|
||||
case '神经网络':
|
||||
shouldShow = index === 6 || index === 7 // 神经网络相关应用(SecondProject, ThirdProject)
|
||||
break
|
||||
case '特征提取':
|
||||
shouldShow = index === 1 || index === 4 // 特征提取相关应用
|
||||
break
|
||||
case '原理解释':
|
||||
shouldShow = index === 2 || index === 5 // 原理解释相关应用
|
||||
break
|
||||
case '大语言模型':
|
||||
shouldShow = index === 7 // GPT相关应用(ThirdProject)
|
||||
break
|
||||
case '声音识别':
|
||||
shouldShow = index === 1 || index === 3 // 声音识别相关应用
|
||||
break
|
||||
case '图像识别':
|
||||
shouldShow = index === 0 || index === 2 // 图像识别相关应用
|
||||
break
|
||||
case '生成式AI':
|
||||
shouldShow = index === 5 || index === 7 // 生成式AI相关应用(FirstProject, ThirdProject)
|
||||
break
|
||||
default:
|
||||
shouldShow = true
|
||||
}
|
||||
}
|
||||
|
||||
// 显示或隐藏卡片
|
||||
cardElement.style.display = shouldShow ? 'flex' : 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 立即体验按钮跳转函数
|
||||
const goToExperience = (index: number) => {
|
||||
const urls = [
|
||||
'http://103.40.14.23:25534',
|
||||
'http://103.40.14.23:25535',
|
||||
'http://103.40.14.23:25536'
|
||||
'http://103.40.14.23:25536',
|
||||
'http://103.40.14.23:25537',
|
||||
'http://103.40.14.23:25538'
|
||||
]
|
||||
|
||||
if (index >= 0 && index < urls.length) {
|
||||
@ -348,6 +435,23 @@ function hookFirstSecondThirdFourth() {
|
||||
|
||||
function bindClicks() {
|
||||
getAllBlocks().forEach(el => {
|
||||
// 跳过"立即体验"按钮和左侧分类按钮
|
||||
if (el.classList.contains('image-text_4') ||
|
||||
el.classList.contains('group_9') ||
|
||||
el.classList.contains('image-text_1') ||
|
||||
el.classList.contains('image-text_2') ||
|
||||
el.classList.contains('image-text_3') ||
|
||||
el.classList.contains('image-text_9') ||
|
||||
el.classList.contains('image-text_10') ||
|
||||
el.classList.contains('image-text_11') ||
|
||||
el.classList.contains('image-text_12') ||
|
||||
el.classList.contains('image-text_13') ||
|
||||
el.classList.contains('group_5') ||
|
||||
el.classList.contains('text_19') ||
|
||||
el.classList.contains('text_20')) {
|
||||
return
|
||||
}
|
||||
|
||||
const txt = el.querySelector('span')?.textContent || el.textContent || ''
|
||||
const title = resolveNavTitle(el, txt)
|
||||
el.classList.add('clickable')
|
||||
@ -357,6 +461,19 @@ function bindClicks() {
|
||||
})
|
||||
|
||||
getNavBlocks().forEach(el => {
|
||||
// 跳过左侧分类按钮
|
||||
if (el.classList.contains('image-text_1') ||
|
||||
el.classList.contains('image-text_2') ||
|
||||
el.classList.contains('image-text_3') ||
|
||||
el.classList.contains('image-text_9') ||
|
||||
el.classList.contains('image-text_10') ||
|
||||
el.classList.contains('image-text_11') ||
|
||||
el.classList.contains('image-text_12') ||
|
||||
el.classList.contains('image-text_13') ||
|
||||
el.classList.contains('group_5')) {
|
||||
return
|
||||
}
|
||||
|
||||
el.addEventListener('click', (e) => {
|
||||
const title = resolveNavTitle(el, (el as HTMLElement).innerText)
|
||||
setSelectionHighlight(title)
|
||||
@ -872,20 +989,36 @@ button:active {
|
||||
white-space: nowrap;
|
||||
line-height: 20px;
|
||||
margin-left: 212px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.text_16.active {
|
||||
color: rgba(0, 136, 209, 1);
|
||||
font-family: PingFangSC-Medium;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text_17 {
|
||||
width: 32px;
|
||||
height: 20px;
|
||||
overflow-wrap: break-word;
|
||||
color: rgba(0, 136, 209, 1);
|
||||
color: rgba(51, 51, 51, 1);
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC-Medium;
|
||||
font-weight: 500;
|
||||
font-family: PingFangSC-Regular;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
line-height: 20px;
|
||||
margin-left: 32px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.text_17.active {
|
||||
color: rgba(0, 136, 209, 1);
|
||||
font-family: PingFangSC-Medium;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text_18 {
|
||||
@ -947,6 +1080,13 @@ button:active {
|
||||
white-space: nowrap;
|
||||
line-height: 22px;
|
||||
margin-left: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.text_19.active {
|
||||
color: rgba(0, 136, 209, 1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text_20 {
|
||||
@ -961,6 +1101,12 @@ button:active {
|
||||
white-space: nowrap;
|
||||
line-height: 22px;
|
||||
margin-left: 176px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.text_20.active {
|
||||
color: rgba(0, 136, 209, 1);
|
||||
}
|
||||
|
||||
.group_6 {
|
||||
@ -1006,6 +1152,30 @@ button:active {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.image-text_1.active .text-group_1,
|
||||
.image-text_2.active .text-group_2,
|
||||
.image-text_3.active .text-group_3,
|
||||
.image-text_9.active .text-group_12,
|
||||
.image-text_10.active .text-group_13,
|
||||
.image-text_11.active .text-group_14,
|
||||
.image-text_12.active .text-group_15,
|
||||
.image-text_13.active .text-group_16 {
|
||||
color: rgba(0, 136, 209, 1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.image-text_1,
|
||||
.image-text_2,
|
||||
.image-text_3,
|
||||
.image-text_9,
|
||||
.image-text_10,
|
||||
.image-text_11,
|
||||
.image-text_12,
|
||||
.image-text_13 {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.image_8 {
|
||||
width: 214px;
|
||||
height: 1px;
|
||||
|
@ -102,7 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { ArrowLeftOutlined } from '@vicons/antd'
|
||||
|
@ -1,50 +1,44 @@
|
||||
<template>
|
||||
<div class="ai-app-container">
|
||||
<!-- 查询区域 -->
|
||||
<n-card class="search-card" :bordered="false">
|
||||
<n-form
|
||||
ref="searchFormRef"
|
||||
:model="searchForm"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="search-form"
|
||||
>
|
||||
<n-grid :cols="24" :x-gap="16" responsive="screen">
|
||||
<n-form-item-gi :span="searchFormSpan.name" label="应用名称" path="name">
|
||||
<div class="search-container">
|
||||
<div class="search-form-wrapper">
|
||||
<div class="search-item">
|
||||
<label class="search-label">应用名称:</label>
|
||||
<n-input
|
||||
v-model:value="searchForm.name"
|
||||
placeholder="请输入应用名称"
|
||||
clearable
|
||||
class="search-input"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="searchFormSpan.type" label="应用类型" path="type">
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<label class="search-label">应用类型:</label>
|
||||
<n-select
|
||||
v-model:value="searchForm.type"
|
||||
placeholder="请选择应用类型"
|
||||
clearable
|
||||
:options="appTypeOptions"
|
||||
class="search-select"
|
||||
/>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="searchFormSpan.actions">
|
||||
<n-space>
|
||||
<n-button type="primary" @click="handleSearch">
|
||||
</div>
|
||||
<div class="search-buttons">
|
||||
<n-button type="primary" @click="handleSearch" class="search-btn">
|
||||
<template #icon>
|
||||
<n-icon><SearchOutlined /></n-icon>
|
||||
</template>
|
||||
查询
|
||||
</n-button>
|
||||
<n-button @click="handleReset">
|
||||
<n-button @click="handleReset" class="reset-btn">
|
||||
<template #icon>
|
||||
<n-icon><ReloadOutlined /></n-icon>
|
||||
</template>
|
||||
重置
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用卡片区域 -->
|
||||
<n-card class="app-cards-container" :bordered="false">
|
||||
@ -158,7 +152,7 @@
|
||||
|
||||
<n-dropdown
|
||||
:options="getActionOptions(item)"
|
||||
@select="(key) => handleActionSelect(key, item)"
|
||||
@select="(key: string) => handleActionSelect(key, item)"
|
||||
>
|
||||
<n-button text>
|
||||
<template #icon>
|
||||
@ -195,20 +189,92 @@
|
||||
<n-modal
|
||||
v-model:show="showAppModal"
|
||||
preset="card"
|
||||
:title="appModalTitle"
|
||||
:style="modalStyle"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<AiAppForm
|
||||
ref="appFormRef"
|
||||
:form-data="currentApp"
|
||||
:is-edit="isEditMode"
|
||||
@submit="handleAppSubmit"
|
||||
<template #header>
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">创建应用</span>
|
||||
<n-icon size="16" class="help-icon">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="create-app-form">
|
||||
<!-- 应用名称 -->
|
||||
<div class="form-item">
|
||||
<label class="form-label required">应用名称</label>
|
||||
<n-input
|
||||
v-model:value="appForm.name"
|
||||
placeholder="请输入应用名称"
|
||||
maxlength="64"
|
||||
show-count
|
||||
class="form-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 应用描述 -->
|
||||
<div class="form-item">
|
||||
<label class="form-label">应用描述</label>
|
||||
<n-input
|
||||
v-model:value="appForm.description"
|
||||
type="textarea"
|
||||
placeholder="简述该应用的适用场景及用途"
|
||||
maxlength="256"
|
||||
show-count
|
||||
:rows="4"
|
||||
class="form-textarea"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 应用图标 -->
|
||||
<div class="form-item">
|
||||
<label class="form-label">应用图标</label>
|
||||
<div class="upload-area">
|
||||
<div class="upload-box">
|
||||
<n-icon size="24" class="upload-icon">
|
||||
<UploadOutlined />
|
||||
</n-icon>
|
||||
<span class="upload-text">上传</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选择应用类型 -->
|
||||
<div class="form-item">
|
||||
<label class="form-label">选择应用类型</label>
|
||||
<div class="app-type-options">
|
||||
<div
|
||||
class="type-option"
|
||||
:class="{ active: appForm.type === 'simple' }"
|
||||
@click="appForm.type = 'simple'"
|
||||
>
|
||||
<n-radio :checked="appForm.type === 'simple'" />
|
||||
<div class="type-content">
|
||||
<div class="type-title">简单配置</div>
|
||||
<div class="type-desc">适合新手创建小助手</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="type-option"
|
||||
:class="{ active: appForm.type === 'advanced' }"
|
||||
@click="appForm.type = 'advanced'"
|
||||
>
|
||||
<n-radio :checked="appForm.type === 'advanced'" />
|
||||
<div class="type-content">
|
||||
<div class="type-title">高级编排</div>
|
||||
<div class="type-desc">适合高级用户自定义小助手的工作流</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="showAppModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="handleAppFormSubmit">确定</n-button>
|
||||
<n-button type="primary" @click="handleCreateSubmit">确认</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
@ -257,17 +323,19 @@ import {
|
||||
SendOutlined,
|
||||
RocketOutlined,
|
||||
GlobalOutlined,
|
||||
MenuOutlined
|
||||
MenuOutlined,
|
||||
QuestionCircleOutlined,
|
||||
UploadOutlined
|
||||
} from '@vicons/antd'
|
||||
import { aiAppApi } from './aiApp'
|
||||
import type { AiApp, SearchForm, Pagination } from './type/aiApp'
|
||||
import AiAppForm from './component/AiAppForm.vue'
|
||||
// import AiAppForm from './component/AiAppForm.vue'
|
||||
import AiAppSetting from './component/AiAppSetting.vue'
|
||||
import AiAppPublish from './component/AiAppPublish.vue'
|
||||
|
||||
// 组件引用
|
||||
const searchFormRef = ref()
|
||||
const appFormRef = ref()
|
||||
// const appFormRef = ref()
|
||||
const appSettingRef = ref()
|
||||
const appPublishRef = ref()
|
||||
|
||||
@ -293,9 +361,35 @@ const showAppModal = ref(false)
|
||||
const showSettingModal = ref(false)
|
||||
const showPublishModal = ref(false)
|
||||
const isEditMode = ref(false)
|
||||
const currentApp = ref<Partial<AiApp>>({})
|
||||
const currentApp = ref<AiApp>({
|
||||
id: '',
|
||||
name: '',
|
||||
descr: '',
|
||||
icon: '',
|
||||
type: 'chatSimple',
|
||||
status: 'enable',
|
||||
modelId: '',
|
||||
flowId: '',
|
||||
knowledgeIds: '',
|
||||
prompt: '',
|
||||
prologue: '',
|
||||
presetQuestion: '',
|
||||
msgNum: 10,
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: ''
|
||||
})
|
||||
const publishType = ref<'web' | 'menu'>('web')
|
||||
|
||||
// 创建应用表单数据
|
||||
const appForm = reactive({
|
||||
name: '',
|
||||
description: '',
|
||||
icon: '',
|
||||
type: 'simple' // simple | advanced
|
||||
})
|
||||
|
||||
// 应用类型选项
|
||||
const appTypeOptions = [
|
||||
{ label: '简单配置', value: 'chatSimple' },
|
||||
@ -303,7 +397,7 @@ const appTypeOptions = [
|
||||
]
|
||||
|
||||
// 计算属性
|
||||
const appModalTitle = computed(() => isEditMode.value ? '编辑应用' : '创建应用')
|
||||
// const appModalTitle = computed(() => isEditMode.value ? '编辑应用' : '创建应用')
|
||||
const publishModalTitle = computed(() => publishType.value === 'web' ? '嵌入网站' : '配置菜单')
|
||||
|
||||
// 响应式网格布局
|
||||
@ -322,19 +416,19 @@ const gridSpan = computed(() => {
|
||||
})
|
||||
|
||||
// 搜索表单响应式布局
|
||||
const searchFormSpan = computed(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const width = window.innerWidth
|
||||
if (width >= 1200) {
|
||||
return { name: 6, type: 6, actions: 6 } // 大屏:三列
|
||||
}
|
||||
if (width >= 768) {
|
||||
return { name: 8, type: 8, actions: 8 } // 中屏:三列紧凑
|
||||
}
|
||||
return { name: 24, type: 24, actions: 24 } // 小屏:单列
|
||||
}
|
||||
return { name: 6, type: 6, actions: 6 } // 默认值
|
||||
})
|
||||
// const searchFormSpan = computed(() => {
|
||||
// if (typeof window !== 'undefined') {
|
||||
// const width = window.innerWidth
|
||||
// if (width >= 1200) {
|
||||
// return { name: 6, type: 6, actions: 6 } // 大屏:三列
|
||||
// }
|
||||
// if (width >= 768) {
|
||||
// return { name: 8, type: 8, actions: 8 } // 中屏:三列紧凑
|
||||
// }
|
||||
// return { name: 24, type: 24, actions: 24 } // 小屏:单列
|
||||
// }
|
||||
// return { name: 6, type: 6, actions: 6 } // 默认值
|
||||
// })
|
||||
|
||||
// 弹窗样式响应式
|
||||
const modalStyle = computed(() => {
|
||||
@ -375,7 +469,17 @@ const publishModalStyle = computed(() => {
|
||||
|
||||
// 获取应用图标
|
||||
const getAppIcon = (icon?: string) => {
|
||||
return icon ? `/api/sys/common/static/${icon}` : '/default-app-icon.png'
|
||||
if (!icon) {
|
||||
return '/default-app-icon.png'
|
||||
}
|
||||
|
||||
// 如果是完整的HTTP URL,直接返回
|
||||
if (icon.startsWith('http://') || icon.startsWith('https://')) {
|
||||
return icon
|
||||
}
|
||||
|
||||
// 否则拼接静态资源路径
|
||||
return `/api/sys/common/static/${icon}`
|
||||
}
|
||||
|
||||
// 获取操作选项
|
||||
@ -422,7 +526,7 @@ const loadAppList = async () => {
|
||||
pageNo: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
order: 'desc' as 'desc',
|
||||
...searchForm
|
||||
}
|
||||
|
||||
@ -469,10 +573,58 @@ const handlePageSizeChange = (pageSize: number) => {
|
||||
// 创建应用
|
||||
const handleCreateApp = () => {
|
||||
isEditMode.value = false
|
||||
currentApp.value = {}
|
||||
currentApp.value = {
|
||||
id: '',
|
||||
name: '',
|
||||
descr: '',
|
||||
icon: '',
|
||||
type: 'chatSimple',
|
||||
status: 'enable',
|
||||
modelId: '',
|
||||
flowId: '',
|
||||
knowledgeIds: '',
|
||||
prompt: '',
|
||||
prologue: '',
|
||||
presetQuestion: '',
|
||||
msgNum: 10,
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: ''
|
||||
}
|
||||
// 重置表单
|
||||
appForm.name = ''
|
||||
appForm.description = ''
|
||||
appForm.icon = ''
|
||||
appForm.type = 'simple'
|
||||
showAppModal.value = true
|
||||
}
|
||||
|
||||
// 创建应用提交
|
||||
const handleCreateSubmit = async () => {
|
||||
if (!appForm.name.trim()) {
|
||||
message.error('请输入应用名称')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = {
|
||||
name: appForm.name,
|
||||
description: appForm.description,
|
||||
icon: appForm.icon,
|
||||
type: (appForm.type === 'simple' ? 'chatSimple' : 'chatFlow') as 'chatSimple' | 'chatFlow'
|
||||
}
|
||||
|
||||
await aiAppApi.saveApp(formData)
|
||||
message.success('创建成功')
|
||||
showAppModal.value = false
|
||||
loadAppList()
|
||||
} catch (error) {
|
||||
message.error('创建失败')
|
||||
console.error('Create app error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑应用
|
||||
const handleEditApp = (app: AiApp) => {
|
||||
isEditMode.value = true
|
||||
@ -560,28 +712,28 @@ const handleRelease = (app: AiApp, toRelease: boolean) => {
|
||||
}
|
||||
|
||||
// 应用表单提交
|
||||
const handleAppFormSubmit = () => {
|
||||
appFormRef.value?.submit()
|
||||
}
|
||||
// const handleAppFormSubmit = () => {
|
||||
// appFormRef.value?.submit()
|
||||
// }
|
||||
|
||||
const handleAppSubmit = async (formData: AiApp) => {
|
||||
try {
|
||||
await aiAppApi.saveApp(formData)
|
||||
message.success(isEditMode.value ? '编辑成功' : '创建成功')
|
||||
showAppModal.value = false
|
||||
|
||||
if (!isEditMode.value) {
|
||||
// 创建成功后打开设置弹窗
|
||||
currentApp.value = formData
|
||||
showSettingModal.value = true
|
||||
}
|
||||
|
||||
loadAppList()
|
||||
} catch (error) {
|
||||
message.error(isEditMode.value ? '编辑失败' : '创建失败')
|
||||
console.error('Save app error:', error)
|
||||
}
|
||||
}
|
||||
// const handleAppSubmit = async (formData: AiApp) => {
|
||||
// try {
|
||||
// await aiAppApi.saveApp(formData)
|
||||
// message.success(isEditMode.value ? '编辑成功' : '创建成功')
|
||||
// showAppModal.value = false
|
||||
//
|
||||
// if (!isEditMode.value) {
|
||||
// // 创建成功后打开设置弹窗
|
||||
// currentApp.value = formData
|
||||
// showSettingModal.value = true
|
||||
// }
|
||||
//
|
||||
// loadAppList()
|
||||
// } catch (error) {
|
||||
// message.error(isEditMode.value ? '编辑失败' : '创建失败')
|
||||
// console.error('Save app error:', error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 设置成功
|
||||
const handleSettingSuccess = () => {
|
||||
@ -610,23 +762,89 @@ onMounted(() => {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
.search-container {
|
||||
margin-bottom: 24px;
|
||||
background: transparent;
|
||||
|
||||
.search-form {
|
||||
.n-form-item {
|
||||
margin-bottom: 0;
|
||||
.search-form-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.search-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.search-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.search-select {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-left: 0;
|
||||
|
||||
.search-btn {
|
||||
background: #1890ff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0 16px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 0 16px;
|
||||
height: 32px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式布局
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
|
||||
.search-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.search-label {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.search-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
margin-left: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-cards-container {
|
||||
background: white;
|
||||
background: transparent !important;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: none !important;
|
||||
|
||||
.add-app-card {
|
||||
height: 180px;
|
||||
@ -638,6 +856,7 @@ onMounted(() => {
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
// 响应式高度
|
||||
@media (max-width: 768px) {
|
||||
@ -651,8 +870,12 @@ onMounted(() => {
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
background-color: #f0f8ff;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.add-app-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -680,7 +903,9 @@ onMounted(() => {
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
// 响应式高度
|
||||
@media (max-width: 768px) {
|
||||
@ -691,11 +916,7 @@ onMounted(() => {
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
|
||||
.app-header {
|
||||
display: flex;
|
||||
@ -773,6 +994,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
:deep(.n-card) {
|
||||
background: transparent !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
|
||||
border: none !important;
|
||||
|
||||
.n-card__content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@ -793,4 +1018,134 @@ onMounted(() => {
|
||||
background-color: rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// 强制覆盖应用卡片容器的样式
|
||||
:deep(.app-cards-container.n-card) {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
:deep(.app-cards-container .n-card__content) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
// 创建应用弹窗样式
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.modal-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.create-app-form {
|
||||
.form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
|
||||
&.required::before {
|
||||
content: '*';
|
||||
color: #ff4d4f;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
.upload-box {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-type-options {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
.type-option {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #1890ff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.type-content {
|
||||
flex: 1;
|
||||
|
||||
.type-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
@ -73,7 +73,7 @@ export const aiAppApi = {
|
||||
* 生成提示词
|
||||
* @param params 生成参数
|
||||
*/
|
||||
generatePrompt(params: { prompt: string }): Promise<ReadableStream> {
|
||||
generatePrompt(params: { prompt: string }): Promise<any> {
|
||||
return http.post(`/airag/app/prompt/generate?prompt=${encodeURIComponent(params.prompt)}`, null, {
|
||||
responseType: 'stream',
|
||||
timeout: 5 * 60 * 1000
|
||||
|
@ -87,7 +87,7 @@
|
||||
class="type-option-card"
|
||||
:class="{ active: formData.type === option.value }"
|
||||
hoverable
|
||||
@click="formData.type = option.value"
|
||||
@click="formData.type = option.value as 'chatSimple' | 'chatFlow'"
|
||||
>
|
||||
<div class="type-option-content">
|
||||
<n-radio :value="option.value" />
|
||||
@ -109,7 +109,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from 'vue'
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { CloudUploadOutlined, RobotOutlined, SettingOutlined } from '@vicons/antd'
|
||||
import type { FormInst, FormRules, UploadFileInfo } from 'naive-ui'
|
||||
@ -141,6 +141,7 @@ const formData = reactive<AppFormData>({
|
||||
descr: '',
|
||||
icon: '',
|
||||
type: 'chatSimple',
|
||||
status: 'enable',
|
||||
...props.formData
|
||||
})
|
||||
|
||||
@ -204,7 +205,7 @@ const handleBeforeUpload = (data: { file: UploadFileInfo }) => {
|
||||
}
|
||||
|
||||
// 上传完成
|
||||
const handleUploadFinish = async ({ file, event }: { file: UploadFileInfo, event?: ProgressEvent }) => {
|
||||
const handleUploadFinish = async ({ file }: { file: UploadFileInfo, event?: ProgressEvent }) => {
|
||||
try {
|
||||
if (file.file) {
|
||||
const response = await uploadApi.uploadImage(file.file)
|
||||
|
@ -208,11 +208,11 @@ const menuConfig = reactive({
|
||||
})
|
||||
|
||||
// 主题选项
|
||||
const themeOptions = [
|
||||
{ label: '浅色主题', value: 'light' },
|
||||
{ label: '深色主题', value: 'dark' },
|
||||
{ label: '自动主题', value: 'auto' }
|
||||
]
|
||||
// const themeOptions = [
|
||||
// { label: '浅色主题', value: 'light' },
|
||||
// { label: '深色主题', value: 'dark' },
|
||||
// { label: '自动主题', value: 'auto' }
|
||||
// ]
|
||||
|
||||
// 预览URL
|
||||
const previewUrl = computed(() => {
|
||||
|
@ -279,7 +279,8 @@
|
||||
>
|
||||
<n-form-item label="选择流程" path="flowId">
|
||||
<n-select
|
||||
v-model:value="configData.flow.flowId"
|
||||
:value="configData.flow?.flowId"
|
||||
@update:value="(value: string) => { if (configData.flow) configData.flow.flowId = value }"
|
||||
placeholder="请选择流程"
|
||||
:options="flowOptions"
|
||||
:loading="loadingFlows"
|
||||
@ -288,7 +289,7 @@
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<div v-if="configData.flow.flowId" class="flow-preview">
|
||||
<div v-if="configData.flow?.flowId" class="flow-preview">
|
||||
<h4>流程预览</h4>
|
||||
<n-card>
|
||||
<n-empty description="流程预览功能开发中" />
|
||||
@ -327,6 +328,7 @@ import {
|
||||
import type { FormInst } from 'naive-ui'
|
||||
import type { AiApp, AppConfig, Knowledge } from '../type/aiApp'
|
||||
import { aiAppApi } from '../aiApp'
|
||||
import { SystemApi } from '@/api'
|
||||
|
||||
interface Props {
|
||||
appData: AiApp
|
||||
@ -356,8 +358,8 @@ const generatingPrompt = ref(false)
|
||||
const showKnowledgeModal = ref(false)
|
||||
|
||||
// 选项数据
|
||||
const modelOptions = ref([])
|
||||
const flowOptions = ref([])
|
||||
const modelOptions = ref<Array<{label: string, value: string}>>([])
|
||||
const flowOptions = ref<Array<{label: string, value: string}>>([])
|
||||
const selectedKnowledges = ref<Knowledge[]>([])
|
||||
|
||||
// 配置数据
|
||||
@ -422,21 +424,45 @@ const flowRules = {
|
||||
const loadModelOptions = async () => {
|
||||
loadingModels.value = true
|
||||
try {
|
||||
// 这里调用获取模型列表的API
|
||||
// const response = await aiModelApi.getModelList()
|
||||
// modelOptions.value = response.result.map(item => ({
|
||||
// label: item.name,
|
||||
// value: item.id
|
||||
// }))
|
||||
console.log('🚀 开始加载AI模型选项...')
|
||||
console.log('🔍 当前时间戳:', Date.now())
|
||||
|
||||
// 模拟数据
|
||||
const response = await SystemApi.getAiModelDict()
|
||||
|
||||
console.log('📦 AI模型API完整响应:', response)
|
||||
console.log('📦 响应数据结构:', {
|
||||
code: response.code,
|
||||
data: response.data,
|
||||
dataType: typeof response.data,
|
||||
dataKeys: response.data ? Object.keys(response.data) : []
|
||||
})
|
||||
|
||||
if (response.code === 200 || response.code === 0) {
|
||||
const dataList = response.data || []
|
||||
modelOptions.value = dataList.map((item: any) => ({
|
||||
label: item.text,
|
||||
value: item.value
|
||||
}))
|
||||
console.log('✅ AI模型列表加载成功:', modelOptions.value)
|
||||
} else {
|
||||
throw new Error(response.message || '获取模型列表失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 加载模型列表失败:', error)
|
||||
console.error('❌ 错误详情:', {
|
||||
message: error.message,
|
||||
response: error.response,
|
||||
config: error.config
|
||||
})
|
||||
|
||||
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' }
|
||||
]
|
||||
} catch (error) {
|
||||
message.error('加载模型列表失败')
|
||||
} finally {
|
||||
loadingModels.value = false
|
||||
}
|
||||
@ -493,7 +519,7 @@ const handleModelChange = (modelId: string) => {
|
||||
'claude-3': { temperature: 0.7, maxTokens: 4000 }
|
||||
}
|
||||
|
||||
const defaults = modelDefaults[modelId]
|
||||
const defaults = modelDefaults[modelId as keyof typeof modelDefaults]
|
||||
if (defaults) {
|
||||
Object.assign(configData.model, defaults)
|
||||
}
|
||||
@ -568,7 +594,8 @@ const handleSave = async () => {
|
||||
prologue: configData.chat.prologue,
|
||||
presetQuestion: configData.chat.presetQuestions?.join('\n') || '',
|
||||
msgNum: configData.chat.msgNum,
|
||||
flowId: configData.flow?.flowId || ''
|
||||
flowId: configData.flow?.flowId || '',
|
||||
type: props.appData.type
|
||||
}
|
||||
|
||||
await aiAppApi.saveApp(saveData)
|
||||
|
@ -60,6 +60,8 @@ export interface AiApp {
|
||||
createBy_dictText?: string
|
||||
/** 创建时间 */
|
||||
createTime?: string
|
||||
/** 更新者 */
|
||||
updateBy?: string
|
||||
/** 更新时间 */
|
||||
updateTime?: string
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class HttpClient {
|
||||
|
||||
constructor(config?: AxiosRequestConfig) {
|
||||
this.instance = axios.create({
|
||||
baseURL: '/api',
|
||||
baseURL: '/jeecgboot',
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
@ -55,7 +55,6 @@ class HttpClient {
|
||||
const token = this.getToken()
|
||||
if (token) {
|
||||
config.headers = config.headers || {}
|
||||
config.headers['Authorization'] = `Bearer ${token}`
|
||||
config.headers['X-Access-Token'] = token
|
||||
}
|
||||
|
||||
@ -89,14 +88,14 @@ class HttpClient {
|
||||
if (requestConfig.showSuccessMessage && requestConfig.successMessage) {
|
||||
this.message.success(requestConfig.successMessage)
|
||||
}
|
||||
return data
|
||||
return response
|
||||
} else {
|
||||
// 处理业务错误
|
||||
const errorMessage = data.message || '请求失败'
|
||||
if (requestConfig.showErrorMessage !== false) {
|
||||
this.message.error(errorMessage)
|
||||
}
|
||||
return Promise.reject(new Error(errorMessage))
|
||||
return Promise.reject(response)
|
||||
}
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
@ -143,7 +142,7 @@ class HttpClient {
|
||||
* 获取认证token
|
||||
*/
|
||||
private getToken(): string | null {
|
||||
return localStorage.getItem('ACCESS_TOKEN') || sessionStorage.getItem('ACCESS_TOKEN')
|
||||
return localStorage.getItem('X-Access-Token') || sessionStorage.getItem('X-Access-Token')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,12 +156,16 @@ class HttpClient {
|
||||
* 处理未授权
|
||||
*/
|
||||
private handleUnauthorized() {
|
||||
console.log('401 Unauthorized - Current token:', this.getToken())
|
||||
console.log('Current path:', window.location.pathname)
|
||||
|
||||
// 清除token
|
||||
localStorage.removeItem('ACCESS_TOKEN')
|
||||
sessionStorage.removeItem('ACCESS_TOKEN')
|
||||
|
||||
// 跳转到登录页
|
||||
if (window.location.pathname !== '/login') {
|
||||
console.log('Redirecting to login page...')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
@ -249,7 +252,7 @@ class HttpClient {
|
||||
export const http = new HttpClient()
|
||||
|
||||
// 导出类型
|
||||
export type { HttpResponse, HttpRequestConfig }
|
||||
// export type { HttpResponse, HttpRequestConfig }
|
||||
export { HttpClient }
|
||||
|
||||
export default http
|
||||
|
@ -62,17 +62,17 @@
|
||||
</div>
|
||||
|
||||
<!-- ai助教 - 已注释 -->
|
||||
<!-- <div class="ai-container">
|
||||
<div class="ai-container">
|
||||
<router-link to="/teacher/ai-assistant" class="ai-tab" @mouseenter="isAiHovered = true"
|
||||
@mouseleave="isAiHovered = false">
|
||||
<img :src="(isAiActive || isAiHovered) ? '/images/aiAssistant/AI助教1.png' : '/images/aiAssistant/AI助教2.png'"
|
||||
alt="ai" />
|
||||
<span>AI助教</span>
|
||||
</router-link>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 智能体编排 - 可展开菜单 - 已注释 -->
|
||||
<!-- <div class="nav-container orchestration-nav">
|
||||
<div class="nav-container orchestration-nav">
|
||||
<div class="nav-item" :class="{ active: activeNavItem === 6 }" @click="toggleOrchestrationMenu">
|
||||
<img :src="activeNavItem === 6 ? '/images/aiAssistant/AI助教1.png' : '/images/aiAssistant/AI助教2.png'" alt="">
|
||||
<span>智能体编排</span>
|
||||
@ -82,7 +82,7 @@
|
||||
</div>
|
||||
|
||||
<div class="submenu-container" :class="{ expanded: orchestrationMenuExpanded }">
|
||||
<router-link to="/teacher/airag/aiapp" class="submenu-item"
|
||||
<router-link to="/teacher/ai/app" class="submenu-item"
|
||||
:class="{ active: activeSubNavItem === 'app-management' }" @click="setActiveSubNavItem('app-management')">
|
||||
<span>AI应用管理</span>
|
||||
</router-link>
|
||||
@ -104,7 +104,7 @@
|
||||
<span>OCR识别</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧路由视图 -->
|
||||
|
Loading…
x
Reference in New Issue
Block a user