feat: Phase 1-3 全部完成 — 沙盒增强、学情分析、学习路径

This commit is contained in:
yuzhiran-dev
2026-05-18 09:48:51 +08:00
commit 11bb86854c
277 changed files with 37755 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
'use client';
import { useEffect, useState } from 'react';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { MessageSquare, Heart } from 'lucide-react';
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api/v1';
interface Prompt {
id: number; title: string; description: string; content: string;
model: string | null; likeCount: number; tags: string | null;
}
function PromptSkeleton() {
return (
<Card className="p-5">
<div className="flex gap-2 mb-2">
<Skeleton className="h-5 w-16 rounded-full" />
<Skeleton className="h-5 w-20 rounded-full" />
</div>
<Skeleton className="h-5 w-3/4 mb-1" />
<Skeleton className="h-4 w-full mb-2" />
<Skeleton className="h-4 w-16" />
</Card>
);
}
export default function PromptsPage() {
const [prompts, setPrompts] = useState<Prompt[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`${API_BASE}/prompts`)
.then(r => r.json()).then(data => setPrompts(data.items || []))
.catch(() => {}).finally(() => setLoading(false));
}, []);
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-10">
<h1 className="text-3xl font-bold text-foreground"></h1>
<p className="mt-2 text-muted-foreground"></p>
</div>
{loading ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[1,2,3,4].map(i => <PromptSkeleton key={i} />)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{prompts.map((prompt) => (
<Card key={prompt.id} className="p-5 hover:shadow-md transition-shadow group">
<div className="flex items-center gap-2 mb-2 flex-wrap">
{prompt.tags?.split(',').slice(0, 2).map(tag => (
<Badge key={tag} variant="default">{tag.trim()}</Badge>
))}
{prompt.model && (
<span className="text-xs text-muted-foreground ml-auto">{prompt.model}</span>
)}
</div>
<h3 className="font-semibold group-hover:text-brand-600 transition-colors mb-1">{prompt.title}</h3>
<p className="text-sm text-muted-foreground mb-3 line-clamp-2">{prompt.description || prompt.content}</p>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Heart className="w-3.5 h-3.5" />
<span>{prompt.likeCount}</span>
</div>
</Card>
))}
</div>
)}
</div>
);
}