- 新增模型 API 路由,支持 CRUD 和测试连接 - 支持 MiniMax、GLM、OpenAI Compatible 三种供应商 - 添加连接状态持久化 (untested/connected/disconnected) - 修复 CORS 和数据库模型兼容性问题 - 前端 UI 优化:供应商默认 API 地址自动填充 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
289 lines
9.1 KiB
Vue
289 lines
9.1 KiB
Vue
<template>
|
||
<div class="home">
|
||
<!-- Hero Section -->
|
||
<section class="hero">
|
||
<div class="hero-content">
|
||
<!-- Logo -->
|
||
<div class="hero-logo">
|
||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<defs>
|
||
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" style="stop-color:#00d4ff"/>
|
||
<stop offset="100%" style="stop-color:#7c3aed"/>
|
||
</linearGradient>
|
||
</defs>
|
||
<!-- 外圈 - 数据集合 -->
|
||
<rect x="4" y="4" width="48" height="48" rx="12" stroke="url(#logoGradient)" stroke-width="2.5" fill="none" opacity="0.3"/>
|
||
<!-- Y 字母 - 数据流/分支 -->
|
||
<path d="M18 42V22L28 12V18" stroke="url(#logoGradient)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||
<path d="M28 18L38 28" stroke="url(#logoGradient)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
||
<!-- 数据节点 - 神经网络样式 -->
|
||
<circle cx="18" cy="42" r="3" fill="#00d4ff"/>
|
||
<circle cx="28" cy="12" r="3" fill="#7c3aed"/>
|
||
<circle cx="38" cy="28" r="3" fill="#00d4ff"/>
|
||
<circle cx="28" cy="18" r="2.5" fill="#00d4ff" opacity="0.7"/>
|
||
<!-- 连接线 - 数据流向 -->
|
||
<circle cx="28" cy="32" r="2" fill="#7c3aed" opacity="0.5"/>
|
||
<circle cx="20" cy="32" r="1.5" fill="#00d4ff" opacity="0.4"/>
|
||
<circle cx="36" cy="38" r="1.5" fill="#7c3aed" opacity="0.4"/>
|
||
</svg>
|
||
<span class="logo-text">YG<span class="logo-highlight">Datasets</span></span>
|
||
</div>
|
||
|
||
<div class="hero-badge">
|
||
<span class="badge-dot"></span>
|
||
<span>AI 驱动数据生成</span>
|
||
</div>
|
||
<h1 class="hero-title">
|
||
构建高质量<br />
|
||
<span class="glow-text">训练数据集</span>
|
||
</h1>
|
||
<p class="hero-subtitle">
|
||
通过智能分割、AI 生成问答和无缝评估,
|
||
将文档转化为结构化数据集。
|
||
</p>
|
||
<div class="hero-actions">
|
||
<el-button type="primary" size="large" @click="createProject" class="btn-primary">
|
||
<el-icon><Plus /></el-icon>
|
||
创建项目
|
||
</el-button>
|
||
<el-button size="large" @click="goToModels" class="btn-secondary">
|
||
<el-icon><Cpu /></el-icon>
|
||
模型管理
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hero Visual - Modern Abstract Composition -->
|
||
<div class="hero-visual">
|
||
<!-- Light rays -->
|
||
<div class="light-rays">
|
||
<div class="ray"></div>
|
||
<div class="ray"></div>
|
||
<div class="ray"></div>
|
||
<div class="ray"></div>
|
||
<div class="ray"></div>
|
||
</div>
|
||
|
||
<!-- Ambient particles -->
|
||
<span class="ambient-particle"></span>
|
||
<span class="ambient-particle"></span>
|
||
<span class="ambient-particle"></span>
|
||
<span class="ambient-particle"></span>
|
||
<span class="ambient-particle"></span>
|
||
|
||
<!-- Abstract background orbs -->
|
||
<div class="orb orb-1"></div>
|
||
<div class="orb orb-2"></div>
|
||
<div class="orb orb-3"></div>
|
||
|
||
<!-- Central floating UI element -->
|
||
<div class="floating-ui">
|
||
<div class="ui-header">
|
||
<div class="ui-dot"></div>
|
||
<div class="ui-dot"></div>
|
||
<div class="ui-dot"></div>
|
||
</div>
|
||
<div class="ui-content">
|
||
<div class="ui-line"></div>
|
||
<div class="ui-line short"></div>
|
||
<div class="ui-line"></div>
|
||
</div>
|
||
<div class="ui-badge">
|
||
<el-icon><Check /></el-icon>
|
||
<span>处理完成</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Floating feature pills - main features -->
|
||
<div class="feature-pill pill-1">
|
||
<el-icon><Document /></el-icon>
|
||
<span>多格式支持</span>
|
||
</div>
|
||
<div class="feature-pill pill-2">
|
||
<el-icon><MagicStick /></el-icon>
|
||
<span>AI 生成</span>
|
||
</div>
|
||
<div class="feature-pill pill-3">
|
||
<el-icon><DataAnalysis /></el-icon>
|
||
<span>智能评估</span>
|
||
</div>
|
||
|
||
<!-- Additional floating labels -->
|
||
<div class="feature-pill pill-4">
|
||
<el-icon><Connection /></el-icon>
|
||
<span>API 集成</span>
|
||
</div>
|
||
<div class="feature-pill pill-5">
|
||
<el-icon><Clock /></el-icon>
|
||
<span>批量处理</span>
|
||
</div>
|
||
<div class="feature-pill pill-6">
|
||
<el-icon><Lock /></el-icon>
|
||
<span>数据安全</span>
|
||
</div>
|
||
<div class="feature-pill pill-7">
|
||
<el-icon><TrendCharts /></el-icon>
|
||
<span>可视化</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Projects Section -->
|
||
<section class="projects-section">
|
||
<div class="section-header">
|
||
<div class="section-title">
|
||
<h2>我的项目</h2>
|
||
<p>{{ projects.length }} 个项目</p>
|
||
</div>
|
||
<el-button type="primary" @click="createProject" class="add-btn">
|
||
<el-icon><Plus /></el-icon>
|
||
新建
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- Projects Grid -->
|
||
<div class="projects-grid" v-loading="loading">
|
||
<!-- Empty State -->
|
||
<EmptyState
|
||
v-if="!loading && projects.length === 0"
|
||
:icon="FolderAdd"
|
||
title="暂无项目"
|
||
description="创建您的第一个项目开始生成数据集"
|
||
action-text="创建项目"
|
||
@action="createProject"
|
||
/>
|
||
|
||
<!-- Project Cards -->
|
||
<ProjectCard
|
||
v-else
|
||
v-for="(project, index) in projects"
|
||
:key="project.id"
|
||
:project="project"
|
||
:index="index"
|
||
@click="openProject"
|
||
@delete="confirmDelete"
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Create Dialog -->
|
||
<CreateProjectDialog
|
||
v-model:visible="dialogVisible"
|
||
:loading="submitting"
|
||
@submit="handleCreateSubmit"
|
||
/>
|
||
|
||
<!-- Delete Confirmation Dialog -->
|
||
<DeleteDialog
|
||
v-model:visible="deleteDialogVisible"
|
||
:item-name="projectToDelete?.name"
|
||
:loading="deleting"
|
||
@confirm="handleDelete"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage } from 'element-plus'
|
||
import { FolderAdd, Check, Connection, Clock, Lock, TrendCharts } from '@element-plus/icons-vue'
|
||
import { projectApi } from '@/api'
|
||
import type { Project, ProjectCreate } from '@/types'
|
||
|
||
// Components
|
||
import EmptyState from '@/components/common/EmptyState.vue'
|
||
import ProjectCard from '@/components/common/ProjectCard.vue'
|
||
import CreateProjectDialog from '@/components/common/CreateProjectDialog.vue'
|
||
import DeleteDialog from '@/components/common/DeleteDialog.vue'
|
||
|
||
const router = useRouter()
|
||
|
||
const loading = ref(false)
|
||
const projects = ref([])
|
||
const dialogVisible = ref(false)
|
||
const deleteDialogVisible = ref(false)
|
||
const projectToDelete = ref(null)
|
||
const submitting = ref(false)
|
||
const deleting = ref(false)
|
||
|
||
const fetchProjects = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await projectApi.list()
|
||
// New paginated format: {items: [...], total, page, page_size}
|
||
projects.value = res.items || res || []
|
||
} catch (error) {
|
||
projects.value = []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const createProject = () => {
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
const handleCreateSubmit = async (formData) => {
|
||
// Simple validation
|
||
if (!formData.name || formData.name.trim() === '') {
|
||
ElMessage.warning('请输入项目名称')
|
||
return
|
||
}
|
||
|
||
console.log('Creating project with form:', formData)
|
||
submitting.value = true
|
||
try {
|
||
const res = await projectApi.create(formData)
|
||
console.log('Create response:', res)
|
||
ElMessage.success('项目创建成功')
|
||
dialogVisible.value = false
|
||
fetchProjects()
|
||
// New format: {id: "..."}
|
||
const projectId = res.id
|
||
console.log('Navigating to:', projectId)
|
||
router.push(`/project/${projectId}`)
|
||
} catch (error) {
|
||
console.error('Create project error:', error)
|
||
ElMessage.error('创建项目失败: ' + (error.message || '未知错误'))
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
const openProject = (project) => {
|
||
router.push(`/project/${project.id}`)
|
||
}
|
||
|
||
const confirmDelete = (project) => {
|
||
projectToDelete.value = project
|
||
deleteDialogVisible.value = true
|
||
}
|
||
|
||
const handleDelete = async () => {
|
||
if (!projectToDelete.value) return
|
||
deleting.value = true
|
||
try {
|
||
await projectApi.delete(projectToDelete.value.id)
|
||
ElMessage.success('项目已删除')
|
||
deleteDialogVisible.value = false
|
||
projectToDelete.value = null
|
||
fetchProjects()
|
||
} catch (error) {
|
||
ElMessage.error('删除失败')
|
||
} finally {
|
||
deleting.value = false
|
||
}
|
||
}
|
||
|
||
const goToDataSquare = () => router.push('/data-square')
|
||
const goToModels = () => router.push('/models')
|
||
|
||
onMounted(() => fetchProjects())
|
||
</script>
|
||
|
||
<style scoped>
|
||
@import '@/styles/pages/home.scss';
|
||
</style>
|