feat:精选资源
This commit is contained in:
parent
42d84ee2fa
commit
3ff9e43877
@ -19,6 +19,8 @@ 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'
|
||||
export { ResourceApi } from './modules/resource'
|
||||
export type { FeaturedResource } from './modules/resource'
|
||||
|
||||
// API 基础配置
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/jeecgboot'
|
||||
|
@ -708,6 +708,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: Resources,
|
||||
meta: { title: '精选资源' }
|
||||
},
|
||||
{
|
||||
path: '/resource-test',
|
||||
name: 'ResourceTest',
|
||||
component: () => import('@/views/ResourceTest.vue'),
|
||||
meta: { title: '精选资源API测试' }
|
||||
},
|
||||
{
|
||||
path: '/special-training',
|
||||
name: 'SpecialTraining',
|
||||
|
@ -174,28 +174,28 @@ class HttpClient {
|
||||
* GET请求
|
||||
*/
|
||||
get<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>> {
|
||||
return this.instance.get(url, config)
|
||||
return this.instance.get(url, config).then(response => response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求
|
||||
*/
|
||||
post<T = any>(url: string, data?: any, config?: HttpRequestConfig): Promise<HttpResponse<T>> {
|
||||
return this.instance.post(url, data, config)
|
||||
return this.instance.post(url, data, config).then(response => response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT请求
|
||||
*/
|
||||
put<T = any>(url: string, data?: any, config?: HttpRequestConfig): Promise<HttpResponse<T>> {
|
||||
return this.instance.put(url, data, config)
|
||||
return this.instance.put(url, data, config).then(response => response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE请求
|
||||
*/
|
||||
delete<T = any>(url: string, config?: HttpRequestConfig): Promise<HttpResponse<T>> {
|
||||
return this.instance.delete(url, config)
|
||||
return this.instance.delete(url, config).then(response => response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,22 +13,40 @@
|
||||
<!-- 精选视频区域 -->
|
||||
<section class="featured-videos">
|
||||
<h2 class="section-title">精选视频</h2>
|
||||
<div class="featured-grid">
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在加载精选资源...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error-container">
|
||||
<p class="error-message">{{ error }}</p>
|
||||
<button @click="fetchFeaturedResources" class="retry-button">重试</button>
|
||||
</div>
|
||||
|
||||
<!-- 精选视频网格 -->
|
||||
<div v-else class="featured-grid">
|
||||
<div v-for="video in featuredVideos" :key="video.id" class="featured-card" @click="handleVideoClick(video)">
|
||||
<div class="card-image">
|
||||
<img :src="video.image" :alt="video.title" class="video-thumbnail" />
|
||||
<div class="duration-badge">
|
||||
<img
|
||||
:src="video.thumbnailUrl || video.image"
|
||||
:alt="video.name || video.title"
|
||||
class="video-thumbnail"
|
||||
/>
|
||||
<div v-if="video.type === 0 && video.duration" class="duration-badge">
|
||||
<img src="/images/Featured_resources/duration.png" alt="时长" class="duration-icon">
|
||||
42:52
|
||||
{{ formatDuration(video.duration) }}
|
||||
</div>
|
||||
<div class="play-button">
|
||||
<div v-if="video.type === 0" class="play-button">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M8 5V19L19 12L8 5Z" fill="white" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">{{ video.title }}</h3>
|
||||
<h3 class="card-title">{{ video.name || video.title }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,7 +119,7 @@
|
||||
<div v-if="showVideoModal" class="video-modal-overlay" @click="closeVideoModal">
|
||||
<div class="video-modal" @click.stop>
|
||||
<div class="video-modal-header">
|
||||
<h3 class="video-modal-title">{{ currentVideo?.title || '视频播放' }}</h3>
|
||||
<h3 class="video-modal-title">{{ currentVideo?.name || currentVideo?.title || '视频播放' }}</h3>
|
||||
<button class="close-btn" @click="closeVideoModal">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
||||
@ -109,8 +127,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="video-modal-body">
|
||||
<DPlayerVideo ref="videoPlayerRef" :video-url="currentVideoUrl" :placeholder-image="currentVideo?.image"
|
||||
:placeholder-text="'点击播放视频'" :title="currentVideo?.title || '视频播放'" @play="handleVideoPlay"
|
||||
<DPlayerVideo ref="videoPlayerRef" :video-url="currentVideoUrl" :placeholder-image="currentVideo?.thumbnailUrl || currentVideo?.image"
|
||||
:placeholder-text="'点击播放视频'" :title="currentVideo?.name || currentVideo?.title || '视频播放'" @play="handleVideoPlay"
|
||||
@pause="handleVideoPause" @ended="handleVideoEnded" @error="handleVideoError" />
|
||||
</div>
|
||||
</div>
|
||||
@ -119,8 +137,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { ref, nextTick, onMounted } from 'vue'
|
||||
import DPlayerVideo from '@/components/course/DPlayerVideo.vue'
|
||||
import { ResourceApi, type FeaturedResource } from '@/api/modules/resource'
|
||||
|
||||
// 视频播放弹窗相关状态
|
||||
const showVideoModal = ref(false)
|
||||
@ -137,26 +156,9 @@ const VIDEO_CONFIG = {
|
||||
}
|
||||
|
||||
// 精选视频数据
|
||||
const featuredVideos = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/精选视频1.png',
|
||||
videoUrl: VIDEO_CONFIG.LOCAL // 添加视频URL
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '华南工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/精选视频2.png',
|
||||
videoUrl: VIDEO_CONFIG.LOCAL
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '西安工业大学内部资源之一',
|
||||
image: '/images/Featured_resources/精品视频3.png',
|
||||
videoUrl: VIDEO_CONFIG.LOCAL
|
||||
}
|
||||
])
|
||||
const featuredVideos = ref<FeaturedResource[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
// 视频筛选标签
|
||||
const videoTabs = ref([
|
||||
@ -276,16 +278,94 @@ const allImages = ref([
|
||||
}
|
||||
])
|
||||
|
||||
// 获取精选资源数据
|
||||
const fetchFeaturedResources = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
const response = await ResourceApi.getFeaturedResources()
|
||||
if (response.code === 200 && response.data?.result) {
|
||||
featuredVideos.value = response.data.result
|
||||
console.log('✅ 精选资源加载成功:', featuredVideos.value)
|
||||
} else {
|
||||
throw new Error(response.message || '获取精选资源失败')
|
||||
}
|
||||
} catch (err: any) {
|
||||
error.value = err.message || '获取精选资源失败'
|
||||
console.error('❌ 获取精选资源失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时长显示
|
||||
const formatDuration = (seconds: number): string => {
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const secs = seconds % 60
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||
} else {
|
||||
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
|
||||
// 解析视频URL(支持多清晰度)
|
||||
const parseVideoUrls = (fileUrl: string): string[] => {
|
||||
if (!fileUrl) return []
|
||||
return fileUrl.split(',').map(url => url.trim()).filter(url => url)
|
||||
}
|
||||
|
||||
// 获取最佳视频URL
|
||||
const getBestVideoUrl = (fileUrl: string): string => {
|
||||
console.log('🔍 解析视频URL:', fileUrl)
|
||||
const urls = parseVideoUrls(fileUrl)
|
||||
console.log('🔍 解析后的URL数组:', urls)
|
||||
|
||||
if (urls.length === 0) {
|
||||
console.warn('⚠️ 没有有效的视频URL,使用本地视频作为备用')
|
||||
return VIDEO_CONFIG.LOCAL
|
||||
}
|
||||
|
||||
// 优先选择720p,如果没有则选择第一个
|
||||
const preferredUrl = urls.find(url => url.includes('720p')) || urls[0]
|
||||
console.log('✅ 选择的视频URL:', preferredUrl)
|
||||
return preferredUrl
|
||||
}
|
||||
|
||||
// 视频播放相关方法
|
||||
const handleVideoClick = async (video: any) => {
|
||||
console.log('点击视频:', video.title)
|
||||
const handleVideoClick = async (video: FeaturedResource) => {
|
||||
// 只有视频类型才能播放
|
||||
if (video.type !== 0) {
|
||||
console.log('这是图片资源,不能播放')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('🎬 点击视频:', video.name)
|
||||
console.log('🔍 视频数据:', {
|
||||
id: video.id,
|
||||
name: video.name,
|
||||
type: video.type,
|
||||
fileUrl: video.fileUrl,
|
||||
thumbnailUrl: video.thumbnailUrl
|
||||
})
|
||||
|
||||
currentVideo.value = video
|
||||
currentVideoUrl.value = video.videoUrl || VIDEO_CONFIG.LOCAL
|
||||
|
||||
// 获取最佳视频URL
|
||||
const videoUrl = getBestVideoUrl(video.fileUrl)
|
||||
currentVideoUrl.value = videoUrl
|
||||
|
||||
console.log('🎯 最终使用的视频URL:', currentVideoUrl.value)
|
||||
|
||||
showVideoModal.value = true
|
||||
|
||||
// 等待弹窗显示后初始化播放器
|
||||
await nextTick()
|
||||
if (videoPlayerRef.value) {
|
||||
console.log('🔧 初始化播放器,URL:', currentVideoUrl.value)
|
||||
await videoPlayerRef.value.initializePlayer(currentVideoUrl.value)
|
||||
}
|
||||
}
|
||||
@ -301,6 +381,24 @@ const closeVideoModal = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 图片加载处理
|
||||
const handleImageError = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement
|
||||
console.error('❌ 图片加载失败:', img.src)
|
||||
// 设置默认图片
|
||||
img.src = '/images/Featured_resources/default-thumbnail.png'
|
||||
}
|
||||
|
||||
const handleImageLoad = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement
|
||||
console.log('✅ 图片加载成功:', img.src)
|
||||
}
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
fetchFeaturedResources()
|
||||
})
|
||||
|
||||
// DPlayer 事件处理方法
|
||||
const handleVideoPlay = () => {
|
||||
console.log('视频开始播放')
|
||||
@ -370,6 +468,53 @@ const handleVideoError = (error: any) => {
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
/* 加载和错误状态 */
|
||||
.loading-container,
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #0088D1;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4757;
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
background: #0088D1;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.retry-button:hover {
|
||||
background: #0066A1;
|
||||
}
|
||||
|
||||
.featured-grid {
|
||||
margin: 0 auto;
|
||||
width: 1420px;
|
||||
@ -677,6 +822,11 @@ const handleVideoError = (error: any) => {
|
||||
.all-videos {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.loading-container,
|
||||
.error-container {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
@ -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>
|
||||
@ -104,7 +104,7 @@
|
||||
<span>OCR识别</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 右侧路由视图 -->
|
||||
|
@ -1,71 +1,60 @@
|
||||
<template>
|
||||
<div class="development-page">
|
||||
<div class="page-header">
|
||||
<h1>AI应用管理</h1>
|
||||
<p>管理和配置各种AI应用</p>
|
||||
</div>
|
||||
|
||||
<div class="development-notice">
|
||||
<div class="notice-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>功能开发中</h2>
|
||||
<p>AI应用管理功能正在紧张开发中,敬请期待!</p>
|
||||
<div class="redirect-container">
|
||||
<div class="redirect-message" v-if="!redirected">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在跳转到AI应用管理页面...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// AI应用管理页面
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const redirected = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// 立即跳转到真实的AI应用管理页面
|
||||
redirected.value = true
|
||||
router.replace('/teacher/ai/app')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.development-page {
|
||||
.redirect-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
.redirect-message {
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 32px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #1890ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 16px;
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.redirect-message p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.development-notice {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
.notice-icon {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.development-notice h2 {
|
||||
font-size: 24px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.development-notice p {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,33 +1,55 @@
|
||||
<template>
|
||||
<div class="development-page">
|
||||
<div class="flow-design-page">
|
||||
<div class="page-header">
|
||||
<h1>AI流程设计</h1>
|
||||
<p>可视化设计AI工作流程</p>
|
||||
</div>
|
||||
|
||||
<div class="development-notice">
|
||||
<div class="coming-soon-notice">
|
||||
<div class="notice-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>功能开发中</h2>
|
||||
<p>AI流程设计功能正在紧张开发中,敬请期待!</p>
|
||||
<h2>即将上线</h2>
|
||||
<p>AI流程设计功能即将上线,将支持:</p>
|
||||
<ul class="feature-list">
|
||||
<li>拖拽式流程设计器</li>
|
||||
<li>多种AI节点类型</li>
|
||||
<li>条件分支和循环</li>
|
||||
<li>实时流程预览</li>
|
||||
<li>流程模板库</li>
|
||||
</ul>
|
||||
<div class="action-buttons">
|
||||
<button class="btn-primary" @click="handleNotifyMe">通知我上线</button>
|
||||
<button class="btn-secondary" @click="handleViewDemo">查看演示</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// AI流程设计页面
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const handleNotifyMe = () => {
|
||||
message.success('已为您开启上线通知,我们会在功能上线时第一时间通知您!')
|
||||
}
|
||||
|
||||
const handleViewDemo = () => {
|
||||
message.info('演示功能正在准备中,敬请期待!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.development-page {
|
||||
.flow-design-page {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@ -39,6 +61,7 @@
|
||||
font-size: 32px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
@ -46,26 +69,117 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.development-notice {
|
||||
.coming-soon-notice {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
padding: 60px 24px;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||
border-radius: 12px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #91d5ff;
|
||||
box-shadow: 0 4px 20px rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
.notice-icon {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.development-notice h2 {
|
||||
font-size: 24px;
|
||||
.coming-soon-notice h2 {
|
||||
font-size: 28px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.development-notice p {
|
||||
.coming-soon-notice > p {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin: 24px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 8px 0;
|
||||
color: #333;
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.feature-list li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #1890ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-top: 32px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #40a9ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: #1890ff;
|
||||
border: 1px solid #1890ff;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #f0f9ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.flow-design-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.coming-soon-notice {
|
||||
padding: 40px 16px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,71 +1,10 @@
|
||||
<template>
|
||||
<div class="development-page">
|
||||
<div class="page-header">
|
||||
<h1>AI知识库</h1>
|
||||
<p>构建和管理AI知识库</p>
|
||||
</div>
|
||||
|
||||
<div class="development-notice">
|
||||
<div class="notice-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>功能开发中</h2>
|
||||
<p>AI知识库功能正在紧张开发中,敬请期待!</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 直接使用真实的AI知识库管理组件 -->
|
||||
<AiKnowledgeBaseList />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// AI知识库页面
|
||||
import AiKnowledgeBaseList from '@/views/teacher/ai-knowledge-naive-ui/AiKnowledgeBaseList.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.development-page {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 32px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.development-notice {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
.notice-icon {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.development-notice h2 {
|
||||
font-size: 24px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.development-notice p {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,26 +1,176 @@
|
||||
<template>
|
||||
<div class="development-page">
|
||||
<div class="model-config-page">
|
||||
<div class="page-header">
|
||||
<h1>AI模型配置</h1>
|
||||
<p>配置和优化AI模型参数</p>
|
||||
</div>
|
||||
|
||||
<div class="development-notice">
|
||||
<div class="notice-icon">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 17L12 22L22 17" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M2 12L12 17L22 12" stroke="#1890ff" stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>功能开发中</h2>
|
||||
<p>AI模型配置功能正在紧张开发中,敬请期待!</p>
|
||||
</div>
|
||||
<n-card class="config-card" title="模型配置中心">
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane name="llm" tab="大语言模型">
|
||||
<div class="model-section">
|
||||
<n-space vertical size="large">
|
||||
<n-card title="OpenAI GPT 模型" size="small">
|
||||
<n-form :model="openaiConfig" label-placement="left" label-width="120px">
|
||||
<n-form-item label="API Key">
|
||||
<n-input v-model:value="openaiConfig.apiKey" type="password" placeholder="请输入OpenAI API Key" />
|
||||
</n-form-item>
|
||||
<n-form-item label="模型版本">
|
||||
<n-select v-model:value="openaiConfig.model" :options="gptModels" />
|
||||
</n-form-item>
|
||||
<n-form-item label="温度参数">
|
||||
<n-slider v-model:value="openaiConfig.temperature" :min="0" :max="2" :step="0.1" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card title="本地模型" size="small">
|
||||
<n-form :model="localConfig" label-placement="left" label-width="120px">
|
||||
<n-form-item label="模型地址">
|
||||
<n-input v-model:value="localConfig.endpoint" placeholder="http://localhost:8000" />
|
||||
</n-form-item>
|
||||
<n-form-item label="模型名称">
|
||||
<n-input v-model:value="localConfig.modelName" placeholder="chatglm3-6b" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="embedding" tab="向量化模型">
|
||||
<div class="model-section">
|
||||
<n-card title="文本向量化配置" size="small">
|
||||
<n-form :model="embeddingConfig" label-placement="left" label-width="120px">
|
||||
<n-form-item label="向量维度">
|
||||
<n-input-number v-model:value="embeddingConfig.dimension" :min="128" :max="4096" />
|
||||
</n-form-item>
|
||||
<n-form-item label="批处理大小">
|
||||
<n-input-number v-model:value="embeddingConfig.batchSize" :min="1" :max="100" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="test" tab="模型测试">
|
||||
<div class="test-section">
|
||||
<n-space vertical size="large">
|
||||
<n-input
|
||||
v-model:value="testInput"
|
||||
type="textarea"
|
||||
placeholder="输入测试文本..."
|
||||
:rows="4"
|
||||
/>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="handleTest" :loading="testing">
|
||||
测试模型
|
||||
</n-button>
|
||||
<n-button @click="handleClear">清空</n-button>
|
||||
</n-space>
|
||||
<n-card v-if="testResult" title="测试结果" size="small">
|
||||
<pre class="test-result">{{ testResult }}</pre>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="handleReset">重置</n-button>
|
||||
<n-button type="primary" @click="handleSave" :loading="saving">
|
||||
保存配置
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// AI模型配置页面
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 配置数据
|
||||
const openaiConfig = reactive({
|
||||
apiKey: '',
|
||||
model: 'gpt-3.5-turbo',
|
||||
temperature: 0.7
|
||||
})
|
||||
|
||||
const localConfig = reactive({
|
||||
endpoint: '',
|
||||
modelName: ''
|
||||
})
|
||||
|
||||
const embeddingConfig = reactive({
|
||||
dimension: 1536,
|
||||
batchSize: 10
|
||||
})
|
||||
|
||||
// GPT模型选项
|
||||
const gptModels = [
|
||||
{ label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
|
||||
{ label: 'GPT-4', value: 'gpt-4' },
|
||||
{ label: 'GPT-4 Turbo', value: 'gpt-4-turbo' }
|
||||
]
|
||||
|
||||
// 测试相关
|
||||
const testInput = ref('')
|
||||
const testResult = ref('')
|
||||
const testing = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const handleTest = async () => {
|
||||
if (!testInput.value.trim()) {
|
||||
message.warning('请输入测试文本')
|
||||
return
|
||||
}
|
||||
|
||||
testing.value = true
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
testResult.value = `模型响应: 这是对"${testInput.value}"的测试响应。\n\n配置信息:\n- 模型: ${openaiConfig.model}\n- 温度: ${openaiConfig.temperature}\n- 向量维度: ${embeddingConfig.dimension}`
|
||||
message.success('测试完成')
|
||||
} catch (error) {
|
||||
message.error('测试失败')
|
||||
} finally {
|
||||
testing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
testInput.value = ''
|
||||
testResult.value = ''
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
// 模拟保存配置
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
message.success('配置保存成功')
|
||||
} catch (error) {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
openaiConfig.apiKey = ''
|
||||
openaiConfig.model = 'gpt-3.5-turbo'
|
||||
openaiConfig.temperature = 0.7
|
||||
localConfig.endpoint = ''
|
||||
localConfig.modelName = ''
|
||||
embeddingConfig.dimension = 1536
|
||||
embeddingConfig.batchSize = 10
|
||||
message.info('配置已重置')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
Loading…
x
Reference in New Issue
Block a user