Files
YG-Datasets/frontend/src/views/HomeView.vue
Developer 7514e7e763 feat: 完善模型管理功能
- 新增模型 API 路由,支持 CRUD 和测试连接
- 支持 MiniMax、GLM、OpenAI Compatible 三种供应商
- 添加连接状态持久化 (untested/connected/disconnected)
- 修复 CORS 和数据库模型兼容性问题
- 前端 UI 优化:供应商默认 API 地址自动填充

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 23:02:43 +08:00

289 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>