feat: Implement retro-futuristic UI design across frontend pages and components.

This commit is contained in:
lintsinghua 2025-11-27 21:33:51 +08:00
parent 7315770a17
commit 53c8c27ee7
17 changed files with 2787 additions and 2695 deletions

View File

@ -4,48 +4,65 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--background: 210 20% 98%;
--foreground: 220 10% 10%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--card-foreground: 220 10% 10%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--popover-foreground: 220 10% 10%;
--primary: 220 100% 50%;
--primary-foreground: 0 0% 100%;
--secondary: 15 100% 50%;
--secondary-foreground: 0 0% 100%;
--muted: 210 20% 90%;
--muted-foreground: 220 10% 40%;
--accent: 180 100% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 100% 50%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 0%;
--input: 0 0% 0%;
--ring: 220 100% 50%;
--radius: 0rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--background: 220 10% 10%;
--foreground: 210 20% 98%;
--card: 220 10% 15%;
--card-foreground: 210 20% 98%;
--popover: 220 10% 15%;
--popover-foreground: 210 20% 98%;
--primary: 220 100% 50%;
--primary-foreground: 0 0% 100%;
--secondary: 15 100% 50%;
--secondary-foreground: 0 0% 100%;
--muted: 220 10% 20%;
--muted-foreground: 210 20% 60%;
--accent: 180 100% 40%;
--accent-foreground: 0 0% 100%;
--destructive: 0 100% 50%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 100%;
--input: 0 0% 100%;
--ring: 220 100% 50%;
}
}
@ -53,8 +70,48 @@
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
@apply bg-background text-foreground font-mono antialiased;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-display font-bold tracking-tight;
}
}
@layer utilities {
.retro-border {
@apply border-2 border-black;
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
}
.retro-card {
@apply bg-white border-2 border-black p-4;
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
}
.retro-btn {
@apply bg-primary text-white font-bold py-2 px-4 border-2 border-black transition-all;
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
}
.retro-btn:hover {
box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 1);
transform: translate(2px, 2px);
}
.retro-btn:active {
box-shadow: none;
transform: translate(4px, 4px);
}
.retro-input {
@apply bg-white border-2 border-black p-2 focus:outline-none focus:ring-2 focus:ring-primary shadow-none;
}
}

View File

@ -6,7 +6,6 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
GitBranch,
@ -208,7 +207,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
console.log('✅ 任务创建成功:', taskId);
// 记录用户操作
import('@/shared/utils/logger').then(({ logger, LogCategory }) => {
import('@/shared/utils/logger').then(({ logger }) => {
logger.logUserAction('创建审计任务', {
taskId,
projectId: project.id,
@ -299,76 +298,74 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Shield className="w-5 h-5 text-primary" />
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto bg-white border-2 border-black p-0 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none">
<DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
<DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
<Shield className="w-6 h-6 text-black" />
<span></span>
</DialogTitle>
</DialogHeader>
<div className="space-y-6">
<div className="p-6 space-y-6">
{/* 项目选择 */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<Label className="text-base font-medium"></Label>
<Badge variant="outline" className="text-xs">
<Label className="text-base font-bold font-mono uppercase"></Label>
<Badge variant="outline" className="text-xs rounded-none border-black font-mono">
{filteredProjects.length}
</Badge>
</div>
{/* 项目搜索 */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
<Input
placeholder="搜索项目名称..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
className="pl-10 retro-input h-10"
/>
</div>
{/* 项目列表 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-60 overflow-y-auto">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-60 overflow-y-auto p-1">
{loading ? (
<div className="col-span-2 flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<div className="animate-spin rounded-none h-8 w-8 border-4 border-primary border-t-transparent"></div>
</div>
) : filteredProjects.length > 0 ? (
filteredProjects.map((project) => (
<Card
<div
key={project.id}
className={`cursor-pointer transition-all hover:shadow-md ${taskForm.project_id === project.id
? 'ring-2 ring-primary bg-primary/5'
: 'hover:bg-gray-50'
className={`cursor-pointer transition-all border-2 p-4 relative ${taskForm.project_id === project.id
? 'border-primary bg-blue-50 shadow-[4px_4px_0px_0px_rgba(37,99,235,1)] translate-x-[-2px] translate-y-[-2px]'
: 'border-black bg-white hover:bg-gray-50 hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px]'
}`}
onClick={() => setTaskForm({ ...taskForm, project_id: project.id })}
>
<CardContent className="p-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className="font-medium text-sm">{project.name}</h4>
{project.description && (
<p className="text-xs text-gray-500 mt-1 line-clamp-2">
{project.description}
</p>
)}
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-400">
<span>{project.repository_type?.toUpperCase() || 'OTHER'}</span>
<span>{project.default_branch}</span>
</div>
</div>
{taskForm.project_id === project.id && (
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-white"></div>
</div>
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className="font-bold text-sm font-display uppercase">{project.name}</h4>
{project.description && (
<p className="text-xs text-gray-600 mt-1 line-clamp-2 font-mono">
{project.description}
</p>
)}
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-500 font-mono font-bold">
<span className="uppercase">{project.repository_type?.toUpperCase() || 'OTHER'}</span>
<span>{project.default_branch}</span>
</div>
</div>
</CardContent>
</Card>
{taskForm.project_id === project.id && (
<div className="w-5 h-5 bg-primary border-2 border-black flex items-center justify-center">
<div className="w-2 h-2 bg-white"></div>
</div>
)}
</div>
</div>
))
) : (
<div className="col-span-2 text-center py-8 text-gray-500">
<div className="col-span-2 text-center py-8 text-gray-500 font-mono">
<FileText className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">
{searchTerm ? '未找到匹配的项目' : '暂无可用项目'}
@ -381,133 +378,141 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 任务配置 */}
{selectedProject && (
<Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="basic" className="flex items-center space-x-2">
<TabsList className="grid w-full grid-cols-3 bg-gray-100 border-2 border-black p-0 h-12 rounded-none">
<TabsTrigger
value="basic"
className="flex items-center space-x-2 rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
>
<GitBranch className="w-4 h-4" />
<span></span>
</TabsTrigger>
<TabsTrigger value="exclude" className="flex items-center space-x-2">
<TabsTrigger
value="exclude"
className="flex items-center space-x-2 rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
>
<FileText className="w-4 h-4" />
<span></span>
</TabsTrigger>
<TabsTrigger value="advanced" className="flex items-center space-x-2">
<TabsTrigger
value="advanced"
className="flex items-center space-x-2 rounded-none data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
>
<Settings className="w-4 h-4" />
<span></span>
</TabsTrigger>
</TabsList>
<TabsContent value="basic" className="space-y-4 mt-6">
<TabsContent value="basic" className="space-y-4 mt-6 font-mono">
{/* ZIP项目文件上传 */}
{(!selectedProject.repository_url || selectedProject.repository_url.trim() === '') && (
<Card className="bg-amber-50 border-amber-200">
<CardContent className="p-4">
<div className="space-y-3">
{loadingZipFile ? (
<div className="flex items-center space-x-3 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div>
<p className="text-sm text-blue-800">ZIP文件...</p>
<div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="space-y-3">
{loadingZipFile ? (
<div className="flex items-center space-x-3 p-4 bg-blue-50 border-2 border-black">
<div className="animate-spin rounded-none h-5 w-5 border-4 border-blue-600 border-t-transparent"></div>
<p className="text-sm text-blue-800 font-bold">ZIP文件...</p>
</div>
) : zipFile ? (
<div className="flex items-start space-x-3 p-4 bg-green-50 border-2 border-black">
<Info className="w-5 h-5 text-green-600 mt-0.5" />
<div className="flex-1">
<p className="font-bold text-green-900 text-sm uppercase"></p>
<p className="text-xs text-green-700 mt-1 font-bold">
使ZIP文件: {zipFile.name} (
{zipFile.size >= 1024 * 1024
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB`
: zipFile.size >= 1024
? `${(zipFile.size / 1024).toFixed(2)} KB`
: `${zipFile.size} B`
})
</p>
</div>
) : zipFile ? (
<div className="flex items-start space-x-3 p-4 bg-green-50 border border-green-200 rounded-lg">
<Info className="w-5 h-5 text-green-600 mt-0.5" />
<div className="flex-1">
<p className="font-medium text-green-900 text-sm"></p>
<p className="text-xs text-green-700 mt-1">
使ZIP文件: {zipFile.name} (
{zipFile.size >= 1024 * 1024
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB`
: zipFile.size >= 1024
? `${(zipFile.size / 1024).toFixed(2)} KB`
: `${zipFile.size} B`
})
<Button
size="sm"
variant="outline"
onClick={() => {
setZipFile(null);
setHasLoadedZip(false);
}}
className="retro-btn bg-white text-black h-8 text-xs"
>
</Button>
</div>
) : (
<>
<div className="flex items-start space-x-3">
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
<div>
<p className="font-bold text-amber-900 text-sm uppercase">ZIP文件</p>
<p className="text-xs text-amber-700 mt-1 font-bold">
ZIP文件
</p>
</div>
<Button
size="sm"
variant="outline"
onClick={() => {
setZipFile(null);
setHasLoadedZip(false);
}}
>
</Button>
</div>
) : (
<>
<div className="flex items-start space-x-3">
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
<div>
<p className="font-medium text-amber-900 text-sm">ZIP文件</p>
<p className="text-xs text-amber-700 mt-1">
ZIP文件
</p>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="zipFile">ZIP文件</Label>
<Input
id="zipFile"
type="file"
accept=".zip"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
console.log('📁 选择的文件:', {
name: file.name,
size: file.size,
type: file.type,
sizeMB: (file.size / 1024 / 1024).toFixed(2)
});
<div className="space-y-2">
<Label htmlFor="zipFile" className="font-bold uppercase">ZIP文件</Label>
<Input
id="zipFile"
type="file"
accept=".zip"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
console.log('📁 选择的文件:', {
name: file.name,
size: file.size,
type: file.type,
sizeMB: (file.size / 1024 / 1024).toFixed(2)
});
const validation = validateZipFile(file);
if (!validation.valid) {
toast.error(validation.error || "文件无效");
e.target.value = '';
return;
}
setZipFile(file);
setHasLoadedZip(true);
const sizeMB = (file.size / 1024 / 1024).toFixed(2);
const sizeKB = (file.size / 1024).toFixed(2);
const sizeText = file.size >= 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
toast.success(`已选择文件: ${file.name} (${sizeText})`);
const validation = validateZipFile(file);
if (!validation.valid) {
toast.error(validation.error || "文件无效");
e.target.value = '';
return;
}
}}
className="cursor-pointer"
/>
</div>
</>
)}
</div>
</CardContent>
</Card>
setZipFile(file);
setHasLoadedZip(true);
const sizeMB = (file.size / 1024 / 1024).toFixed(2);
const sizeKB = (file.size / 1024).toFixed(2);
const sizeText = file.size >= 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
toast.success(`已选择文件: ${file.name} (${sizeText})`);
}
}}
className="cursor-pointer retro-input pt-1.5"
/>
</div>
</>
)}
</div>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="task_type"></Label>
<Label htmlFor="task_type" className="font-bold uppercase"></Label>
<Select
value={taskForm.task_type}
onValueChange={(value: any) => setTaskForm({ ...taskForm, task_type: value })}
>
<SelectTrigger>
<SelectTrigger className="retro-input h-10 rounded-none border-2 border-black shadow-none focus:ring-0">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<SelectItem value="repository">
<div className="flex items-center space-x-2">
<GitBranch className="w-4 h-4" />
<span></span>
<span className="font-mono"></span>
</div>
</SelectItem>
<SelectItem value="instant">
<div className="flex items-center space-x-2">
<Zap className="w-4 h-4" />
<span></span>
<span className="font-mono"></span>
</div>
</SelectItem>
</SelectContent>
@ -516,44 +521,43 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{taskForm.task_type === "repository" && (selectedProject.repository_url) && (
<div className="space-y-2">
<Label htmlFor="branch_name"></Label>
<Label htmlFor="branch_name" className="font-bold uppercase"></Label>
<Input
id="branch_name"
value={taskForm.branch_name || ""}
onChange={(e) => setTaskForm({ ...taskForm, branch_name: e.target.value })}
placeholder={selectedProject.default_branch || "main"}
className="retro-input h-10"
/>
</div>
)}
</div>
{/* 项目信息展示 */}
<Card className="bg-blue-50 border-blue-200">
<CardContent className="p-4">
<div className="flex items-start space-x-3">
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="text-sm">
<p className="font-medium text-blue-900 mb-1">{selectedProject.name}</p>
<div className="text-blue-700 space-y-1">
{selectedProject.description && (
<p>{selectedProject.description}</p>
)}
<p>{selectedProject.default_branch}</p>
{selectedProject.programming_languages && (
<p>{JSON.parse(selectedProject.programming_languages).join(', ')}</p>
)}
</div>
<div className="bg-blue-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-start space-x-3">
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="text-sm font-mono">
<p className="font-bold text-blue-900 mb-1 uppercase">{selectedProject.name}</p>
<div className="text-blue-800 space-y-1 font-bold">
{selectedProject.description && (
<p>{selectedProject.description}</p>
)}
<p>{selectedProject.default_branch}</p>
{selectedProject.programming_languages && (
<p>{JSON.parse(selectedProject.programming_languages).join(', ')}</p>
)}
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="exclude" className="space-y-4 mt-6">
<TabsContent value="exclude" className="space-y-4 mt-6 font-mono">
<div className="space-y-4">
<div>
<Label className="text-base font-medium"></Label>
<p className="text-sm text-gray-500 mt-1">
<Label className="text-base font-bold uppercase"></Label>
<p className="text-sm text-gray-500 mt-1 font-bold">
</p>
</div>
@ -561,14 +565,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 常用排除模式 */}
<div className="grid grid-cols-2 gap-3">
{commonExcludePatterns.map((pattern) => (
<div key={pattern.value} className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50">
<div key={pattern.value} className="flex items-center space-x-3 p-3 border-2 border-black bg-white hover:bg-gray-50 transition-all">
<Checkbox
checked={taskForm.exclude_patterns.includes(pattern.value)}
onCheckedChange={() => toggleExcludePattern(pattern.value)}
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
/>
<div className="flex-1">
<p className="text-sm font-medium">{pattern.label}</p>
<p className="text-xs text-gray-500">{pattern.description}</p>
<p className="text-sm font-bold uppercase">{pattern.label}</p>
<p className="text-xs text-gray-500 font-bold">{pattern.description}</p>
</div>
</div>
))}
@ -576,7 +581,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 自定义排除模式 */}
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<div className="flex space-x-2">
<Input
placeholder="例如: *.tmp, test/**"
@ -586,6 +591,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
e.currentTarget.value = '';
}
}}
className="retro-input h-10"
/>
<Button
type="button"
@ -595,6 +601,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
addCustomPattern(input.value);
input.value = '';
}}
className="retro-btn bg-white text-black h-10"
>
</Button>
@ -604,13 +611,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 已选择的排除模式 */}
{taskForm.exclude_patterns.length > 0 && (
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<div className="flex flex-wrap gap-2">
{taskForm.exclude_patterns.map((pattern) => (
<Badge
key={pattern}
variant="secondary"
className="cursor-pointer hover:bg-red-100 hover:text-red-800"
className="cursor-pointer hover:bg-red-100 hover:text-red-800 rounded-none border-2 border-black bg-gray-100 text-black font-mono font-bold"
onClick={() => removeExcludePattern(pattern)}
>
{pattern} ×
@ -622,18 +629,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
</div>
</TabsContent>
<TabsContent value="advanced" className="space-y-4 mt-6">
<TabsContent value="advanced" className="space-y-4 mt-6 font-mono">
<div className="space-y-6">
<div>
<Label className="text-base font-medium"></Label>
<p className="text-sm text-gray-500 mt-1">
<Label className="text-base font-bold uppercase"></Label>
<p className="text-sm text-gray-500 mt-1 font-bold">
</p>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-4">
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-3 p-3 border-2 border-black bg-white">
<Checkbox
checked={taskForm.scan_config.include_tests}
onCheckedChange={(checked) =>
@ -642,14 +649,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
scan_config: { ...taskForm.scan_config, include_tests: !!checked }
})
}
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
/>
<div>
<p className="text-sm font-medium"></p>
<p className="text-xs text-gray-500"> *test*, *spec* </p>
<p className="text-sm font-bold uppercase"></p>
<p className="text-xs text-gray-500 font-bold"> *test*, *spec* </p>
</div>
</div>
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-3 p-3 border-2 border-black bg-white">
<Checkbox
checked={taskForm.scan_config.include_docs}
onCheckedChange={(checked) =>
@ -658,17 +666,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
scan_config: { ...taskForm.scan_config, include_docs: !!checked }
})
}
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
/>
<div>
<p className="text-sm font-medium"></p>
<p className="text-xs text-gray-500"> README, docs </p>
<p className="text-sm font-bold uppercase"></p>
<p className="text-xs text-gray-500 font-bold"> README, docs </p>
</div>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="max_file_size"> (KB)</Label>
<Label htmlFor="max_file_size" className="font-bold uppercase"> (KB)</Label>
<Input
id="max_file_size"
type="number"
@ -684,11 +693,12 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
}
min="1"
max="10240"
className="retro-input h-10"
/>
</div>
<div className="space-y-2">
<Label htmlFor="analysis_depth"></Label>
<Label htmlFor="analysis_depth" className="font-bold uppercase"></Label>
<Select
value={taskForm.scan_config.analysis_depth}
onValueChange={(value: any) =>
@ -698,13 +708,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
})
}
>
<SelectTrigger>
<SelectTrigger className="retro-input h-10 rounded-none border-2 border-black shadow-none focus:ring-0">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="basic"></SelectItem>
<SelectItem value="standard"></SelectItem>
<SelectItem value="deep"></SelectItem>
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<SelectItem value="basic" className="font-mono"></SelectItem>
<SelectItem value="standard" className="font-mono"></SelectItem>
<SelectItem value="deep" className="font-mono"></SelectItem>
</SelectContent>
</Select>
</div>
@ -712,39 +722,42 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
</div>
{/* 分析深度说明 */}
<Card className="bg-amber-50 border-amber-200">
<CardContent className="p-4">
<div className="flex items-start space-x-3">
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
<div className="text-sm">
<p className="font-medium text-amber-900 mb-2"></p>
<ul className="text-amber-800 space-y-1 text-xs">
<li> <strong></strong></li>
<li> <strong></strong></li>
<li> <strong></strong></li>
</ul>
</div>
<div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-start space-x-3">
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
<div className="text-sm font-mono">
<p className="font-bold text-amber-900 mb-2 uppercase"></p>
<ul className="text-amber-800 space-y-1 text-xs font-bold">
<li> <strong></strong></li>
<li> <strong></strong></li>
<li> <strong></strong></li>
</ul>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</TabsContent>
</Tabs>
)}
{/* 操作按钮 */}
<div className="flex justify-end space-x-3 pt-4 border-t">
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={creating}>
<div className="flex justify-end space-x-3 pt-6 border-t-2 border-black bg-gray-50 -mx-6 -mb-6 p-6">
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={creating}
className="retro-btn bg-white text-black h-12 px-6 font-bold uppercase"
>
</Button>
<Button
onClick={handleCreateTask}
disabled={!taskForm.project_id || creating}
className="btn-primary"
className="retro-btn bg-primary text-white h-12 px-6 font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]"
>
{creating ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
<div className="animate-spin rounded-none h-4 w-4 border-2 border-white border-t-transparent mr-2"></div>
...
</>
) : (

View File

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react";
import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { Terminal, CheckCircle, XCircle, Loader2, X as XIcon } from "lucide-react";
import { Terminal, X as XIcon } from "lucide-react";
import { cn, calculateTaskProgress } from "@/shared/utils/utils";
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
import { taskControl } from "@/shared/services/taskControl";
@ -112,8 +112,8 @@ export default function TerminalProgressDialog({
// 初始化日志
addLog("🚀 审计任务已启动", "info");
addLog(`<EFBFBD> 任务ID: ${taskId}`, "info");
addLog(`<EFBFBD> 任务类D型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
addLog(`任务ID: ${taskId}`, "info");
addLog(`任务类型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
addLog("⏳ 正在初始化审计环境...", "info");
}
@ -383,42 +383,31 @@ export default function TerminalProgressDialog({
const getLogColor = (type: LogEntry["type"]) => {
switch (type) {
case "success":
return "text-emerald-400";
return "text-green-500";
case "error":
return "text-rose-400";
return "text-red-500";
case "warning":
return "text-amber-400";
return "text-yellow-500";
default:
return "text-gray-200";
return "text-gray-300";
}
};
// 获取状态图标
const getStatusIcon = () => {
if (isFailed) {
return <XCircle className="w-5 h-5 text-rose-400" />;
}
if (isCompleted) {
return <CheckCircle className="w-5 h-5 text-emerald-400" />;
}
return <Loader2 className="w-5 h-5 text-rose-400 animate-spin" />;
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogPortal>
<DialogOverlay />
<DialogOverlay className="bg-black/50 backdrop-blur-sm" />
<DialogPrimitive.Content
className={cn(
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
"w-[90vw] aspect-[16/9]",
"max-w-[1600px] max-h-[900px]",
"p-0 gap-0 rounded-lg overflow-hidden",
"bg-gradient-to-br from-gray-900 via-red-950/30 to-gray-900 border border-red-900/50",
"max-w-[1200px] max-h-[800px]",
"p-0 gap-0 rounded-none overflow-hidden",
"bg-black border-4 border-gray-500 shadow-[10px_10px_0px_0px_rgba(0,0,0,0.5)]",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"duration-200 shadow-2xl"
"duration-200"
)}
onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()}
@ -432,33 +421,46 @@ export default function TerminalProgressDialog({
</VisuallyHidden.Root>
{/* 终端头部 */}
<div className="flex items-center justify-between px-4 py-3 bg-gradient-to-r from-red-950/50 to-gray-900/80 border-b border-red-900/30 backdrop-blur-sm">
<div className="flex items-center justify-between px-4 py-2 bg-gray-300 border-b-4 border-gray-500">
<div className="flex items-center space-x-3">
<Terminal className="w-5 h-5 text-rose-400" />
<span className="text-sm font-medium text-gray-100"></span>
{getStatusIcon()}
<Terminal className="w-5 h-5 text-black" />
<span className="text-sm font-bold text-black uppercase font-display tracking-wider">TERMINAL // 审计进度监控</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-3 h-3 rounded-full bg-emerald-500" />
<div className="w-3 h-3 rounded-full bg-amber-500" />
{/* 模拟窗口控制按钮 */}
<div className="w-4 h-4 border-2 border-black bg-white flex items-center justify-center">
<div className="w-2 h-0.5 bg-black"></div>
</div>
<div className="w-4 h-4 border-2 border-black bg-white flex items-center justify-center">
<div className="w-2 h-2 border border-black"></div>
</div>
<button
className="w-3 h-3 rounded-full bg-rose-500 hover:bg-rose-600 cursor-pointer transition-colors focus:outline-none"
className="w-4 h-4 border-2 border-black bg-primary hover:bg-red-600 cursor-pointer transition-colors focus:outline-none flex items-center justify-center"
onClick={() => onOpenChange(false)}
title="关闭"
aria-label="关闭"
/>
>
<XIcon className="w-3 h-3 text-white" />
</button>
</div>
</div>
{/* 终端内容 */}
<div className="p-6 bg-gradient-to-b from-gray-900/95 to-gray-950/95 overflow-y-auto h-[calc(100%-120px)] font-mono text-sm backdrop-blur-sm">
<div className="space-y-2">
<div className="p-6 bg-black overflow-y-auto h-[calc(100%-100px)] font-mono text-sm relative">
{/* 扫描线效果 */}
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] z-10 bg-[length:100%_2px,3px_100%]"></div>
<div className="space-y-1 relative z-20">
{logs.map((log, index) => (
<div key={index} className="flex items-start space-x-3 hover:bg-red-950/10 px-2 py-1 rounded transition-colors">
<span className="text-rose-800/70 text-xs flex-shrink-0 w-20">
<div key={index} className="flex items-start space-x-3 hover:bg-white/5 px-2 py-0.5 transition-colors">
<span className="text-gray-500 text-xs flex-shrink-0 w-24 font-bold">
[{log.timestamp}]
</span>
<span className={`${getLogColor(log.type)} flex-1`}>
<span className={`${getLogColor(log.type)} flex-1 font-bold tracking-wide`}>
{log.type === 'info' && '> '}
{log.type === 'success' && '✓ '}
{log.type === 'error' && '✗ '}
{log.type === 'warning' && '! '}
{log.message}
</span>
</div>
@ -466,107 +468,63 @@ export default function TerminalProgressDialog({
{/* 光标旋转闪烁效果 */}
{!isCompleted && !isFailed && (
<div className="flex items-center space-x-2 mt-4">
<span className="text-rose-800/70 text-xs w-20">[{currentTime}]</span>
<span className="inline-block text-rose-400 animate-spinner font-bold text-base"></span>
<div className="flex items-center space-x-2 mt-4 px-2">
<span className="text-gray-500 text-xs w-24 font-bold">[{currentTime}]</span>
<span className="inline-block text-green-500 animate-pulse font-bold text-base">_</span>
</div>
)}
{/* 添加自定义动画 */}
<style>{`
@keyframes spinner {
0% {
content: '|';
opacity: 1;
}
25% {
content: '/';
opacity: 0.8;
}
50% {
content: '—';
opacity: 0.6;
}
75% {
content: '\\\\';
opacity: 0.8;
}
100% {
content: '|';
opacity: 1;
}
}
.animate-spinner::before {
content: '|';
animation: spinner-content 0.8s linear infinite;
}
.animate-spinner {
animation: spinner-opacity 0.8s ease-in-out infinite;
}
@keyframes spinner-content {
0% { content: '|'; }
25% { content: '/'; }
50% { content: '—'; }
75% { content: '\\\\'; }
100% { content: '|'; }
}
@keyframes spinner-opacity {
0%, 100% { opacity: 1; }
25%, 75% { opacity: 0.8; }
50% { opacity: 0.6; }
}
`}</style>
<div ref={logsEndRef} />
</div>
</div>
{/* 底部控制和提示 */}
<div className="px-4 py-3 bg-gradient-to-r from-red-950/50 to-gray-900/80 border-t border-red-900/30 backdrop-blur-sm">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-300">
{isCancelled ? "🛑 任务已取消,已分析的结果已保存" :
isCompleted ? "✅ 任务已完成,可以关闭此窗口" :
isFailed ? "❌ 任务失败,请检查配置后重试" :
"⏳ 审计进行中,请勿关闭窗口,过程可能较慢,请耐心等待......"}
<div className="px-4 py-3 bg-gray-200 border-t-4 border-gray-500 flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 border-2 border-black ${isFailed ? 'bg-red-500' : isCompleted ? 'bg-green-500' : 'bg-yellow-400 animate-pulse'}`}></div>
<span className="text-xs font-bold text-black uppercase font-mono tracking-tight">
{isCancelled ? "STATUS: CANCELLED // 任务已取消" :
isCompleted ? "STATUS: COMPLETED // 任务已完成" :
isFailed ? "STATUS: FAILED // 任务失败" :
"STATUS: RUNNING // 审计进行中..."}
</span>
</div>
<div className="flex items-center space-x-2">
{/* 运行中显示取消按钮 */}
{!isCompleted && !isFailed && !isCancelled && (
<Button
size="sm"
variant="outline"
onClick={handleCancel}
className="h-7 text-xs bg-gray-800 border-red-600 text-red-400 hover:bg-red-900 hover:text-red-200"
>
<XIcon className="w-3 h-3 mr-1" />
</Button>
)}
<div className="flex items-center space-x-3">
{/* 运行中显示取消按钮 */}
{!isCompleted && !isFailed && !isCancelled && (
<Button
size="sm"
variant="outline"
onClick={handleCancel}
className="h-8 text-xs bg-white border-2 border-black text-black hover:bg-red-100 hover:text-red-900 font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none"
>
<XIcon className="w-3 h-3 mr-1" />
</Button>
)}
{/* 失败时显示查看日志按钮 */}
{isFailed && (
<button
onClick={() => {
window.open('/logs', '_blank');
}}
className="px-4 py-1.5 bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-500 hover:to-orange-500 text-white rounded text-xs transition-all shadow-lg shadow-yellow-900/50 font-medium"
>
📋
</button>
)}
{/* 失败时显示查看日志按钮 */}
{isFailed && (
<button
onClick={() => {
window.open('/logs', '_blank');
}}
className="px-4 py-1.5 bg-yellow-400 border-2 border-black text-black hover:bg-yellow-500 text-xs font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none transition-all"
>
📋
</button>
)}
{/* 已完成/失败/取消显示关闭按钮 */}
{(isCompleted || isFailed || isCancelled) && (
<button
onClick={() => onOpenChange(false)}
className="px-4 py-1.5 bg-gradient-to-r from-rose-600 to-red-600 hover:from-rose-500 hover:to-red-500 text-white rounded text-xs transition-all shadow-lg shadow-rose-900/50 font-medium"
>
</button>
)}
</div>
{/* 已完成/失败/取消显示关闭按钮 */}
{(isCompleted || isFailed || isCancelled) && (
<button
onClick={() => onOpenChange(false)}
className="px-4 py-1.5 bg-primary border-2 border-black text-white hover:bg-primary/90 text-xs font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none transition-all"
>
</button>
)}
</div>
</div>
</DialogPrimitive.Content>

View File

@ -13,7 +13,8 @@ import {
FileText,
ChevronLeft,
ChevronRight,
Github
Github,
Terminal
} from "lucide-react";
import routes from "@/app/routes";
@ -45,7 +46,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
<Button
variant="ghost"
size="sm"
className="fixed top-4 left-4 z-50 md:hidden bg-white shadow-md border border-gray-200 text-gray-700 hover:bg-gray-50"
className="fixed top-4 left-4 z-50 md:hidden bg-white border-2 border-black shadow-retro text-black hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-retro-hover transition-all"
onClick={() => setMobileOpen(!mobileOpen)}
>
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
@ -54,7 +55,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
{/* Overlay for mobile */}
{mobileOpen && (
<div
className="fixed inset-0 bg-black/20 backdrop-blur-sm z-40 md:hidden transition-opacity"
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 md:hidden"
onClick={() => setMobileOpen(false)}
/>
)}
@ -62,41 +63,37 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
{/* Sidebar */}
<aside
className={`
fixed top-0 left-0 h-screen bg-white/95 backdrop-blur-xl
border-r border-gray-200/80 shadow-[4px_0_24px_-12px_rgba(0,0,0,0.1)]
fixed top-0 left-0 h-screen bg-background
border-r-2 border-black
z-40 transition-all duration-300 ease-in-out
${collapsed ? "w-20" : "w-64"}
${mobileOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"}
`}
>
<div className="flex flex-col h-full">
<div className="flex flex-col h-full relative">
{/* Decorative Grid Background */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
{/* Logo Section */}
<div className="relative flex items-center h-[72px] px-4 border-b border-gray-100">
<div className="relative flex items-center h-[72px] px-4 border-b-2 border-black bg-white z-10">
<Link
to="/"
className={`flex items-center space-x-3 group overflow-hidden transition-all duration-300 ${collapsed ? 'justify-center w-full' : ''}`}
onClick={() => setMobileOpen(false)}
>
<div className="relative flex-shrink-0">
<div className="absolute inset-0 bg-red-500/20 blur-lg rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<img
src="/logo_xcodereviewer.png"
alt="XCodeReviewer Logo"
className="w-9 h-9 rounded-xl shadow-sm relative z-10 group-hover:scale-105 transition-transform duration-300"
/>
<div className="relative flex-shrink-0 border-2 border-black bg-primary p-1 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Terminal className="w-6 h-6 text-white" />
</div>
<div className={`transition-all duration-300 ${collapsed ? 'w-0 opacity-0 overflow-hidden' : 'w-auto opacity-100'}`}>
<span className="text-lg font-bold bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-700 group-hover:from-red-700 group-hover:to-red-500 whitespace-nowrap">
XCodeReviewer
<span className="text-lg font-display font-bold text-black tracking-tighter uppercase">
XCode<span className="text-primary">Reviewer</span>
</span>
</div>
</Link>
{/* Collapse button for desktop - Absolute Positioned */}
<Button
variant="ghost"
size="icon"
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white text-gray-400 hover:text-gray-900 border border-gray-200 shadow-sm z-50 hover:bg-gray-50"
{/* Collapse button for desktop */}
<button
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 bg-white border-2 border-black items-center justify-center hover:bg-primary hover:text-white transition-colors z-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none"
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? (
@ -104,12 +101,12 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
) : (
<ChevronLeft className="w-3 h-3" />
)}
</Button>
</button>
</div>
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-6 px-3">
<div className="space-y-1.5">
<nav className="flex-1 overflow-y-auto py-6 px-3 z-10">
<div className="space-y-2">
{visibleRoutes.map((route) => {
const isActive = location.pathname === route.path;
return (
@ -117,39 +114,37 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
key={route.path}
to={route.path}
className={`
flex items-center space-x-3 px-3 py-2.5 rounded-xl
transition-all duration-200 group relative overflow-hidden
flex items-center space-x-3 px-3 py-3
transition-all duration-200 group relative
border-2
${isActive
? "bg-red-50 text-red-700 font-medium shadow-sm ring-1 ring-red-100"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
? "bg-primary border-black shadow-retro text-white"
: "bg-transparent border-transparent hover:border-black hover:bg-white hover:shadow-retro-hover text-gray-600 hover:text-black"
}
`}
onClick={() => setMobileOpen(false)}
title={collapsed ? route.name : undefined}
>
{/* Active Indicator Bar */}
{isActive && (
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-red-600 rounded-r-full opacity-0 md:opacity-100 transition-opacity" />
)}
{/* Icon */}
<span className={`
flex-shrink-0 transition-colors duration-200
${isActive ? "text-red-600" : "text-gray-400 group-hover:text-gray-600"}
${isActive ? "text-white" : "text-black group-hover:text-black"}
`}>
{routeIcons[route.path] || <LayoutDashboard className="w-5 h-5" />}
</span>
{/* Label */}
{!collapsed && (
<span className="whitespace-nowrap z-10">
<span className="font-mono font-bold tracking-tight uppercase text-sm">
{route.name}
</span>
)}
{/* Hover Effect Background (Subtle) */}
{!isActive && (
<div className="absolute inset-0 bg-gray-100 opacity-0 group-hover:opacity-100 transition-opacity duration-200 -z-0 rounded-xl" />
{/* Glitch Effect on Hover (Optional) */}
{!isActive && !collapsed && (
<span className="absolute right-2 opacity-0 group-hover:opacity-100 text-xs font-bold text-primary animate-pulse">
&lt;/&gt;
</span>
)}
</Link>
);
@ -158,23 +153,23 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
</nav>
{/* Footer with GitHub Link */}
<div className="p-4 border-t border-gray-100 bg-gray-50/50">
<div className="p-4 border-t-2 border-black bg-white z-10">
<a
href="https://github.com/lintsinghua/XCodeReviewer"
target="_blank"
rel="noopener noreferrer"
className={`
flex items-center space-x-3 px-3 py-2.5 rounded-xl
text-gray-500 hover:bg-white hover:text-gray-900 hover:shadow-sm hover:ring-1 hover:ring-gray-200
flex items-center space-x-3 px-3 py-2.5
text-black border-2 border-transparent hover:border-black hover:shadow-retro-hover hover:bg-white
transition-all duration-200 group
`}
title={collapsed ? "GitHub Repository" : undefined}
title={collapsed ? "GitHub 仓库" : undefined}
>
<Github className="w-5 h-5 flex-shrink-0 text-gray-400 group-hover:text-gray-900 transition-colors" />
<Github className="w-5 h-5 flex-shrink-0" />
{!collapsed && (
<div className="flex flex-col">
<span className="font-medium text-sm">GitHub</span>
<span className="text-xs text-gray-400 group-hover:text-gray-500">View Source</span>
<span className="font-mono font-bold text-sm uppercase">GitHub</span>
<span className="text-xs text-gray-500 font-mono">v1.0.0</span>
</div>
)}
</a>

View File

@ -78,18 +78,18 @@ export default function ExportReportDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Download className="w-5 h-5 text-primary" />
<DialogContent className="sm:max-w-[600px] bg-white border-2 border-black p-0 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none">
<DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
<DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
<Download className="w-6 h-6 text-black" />
<span></span>
</DialogTitle>
<DialogDescription>
<DialogDescription className="font-mono text-xs text-gray-500 mt-2">
</DialogDescription>
</DialogHeader>
<div className="py-6">
<div className="p-6">
<RadioGroup
value={selectedFormat}
onValueChange={(value) => setSelectedFormat(value as ExportFormat)}
@ -108,29 +108,27 @@ export default function ExportReportDialog({
/>
<Label
htmlFor={format.value}
className={`flex items-start space-x-4 p-4 rounded-lg border-2 cursor-pointer transition-all ${isSelected
? `${format.borderColor} ${format.bgColor} shadow-md`
: "border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"
className={`flex items-start space-x-4 p-4 border-2 cursor-pointer transition-all rounded-none font-mono ${isSelected
? "border-black bg-gray-100 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] translate-x-[-2px] translate-y-[-2px]"
: "border-black bg-white hover:bg-gray-50 hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
}`}
>
<div
className={`w-12 h-12 rounded-lg flex items-center justify-center ${isSelected ? format.bgColor : "bg-gray-50"
className={`w-12 h-12 border-2 border-black flex items-center justify-center rounded-none ${isSelected ? "bg-black text-white" : "bg-white text-black"
}`}
>
<Icon className={`w-6 h-6 ${isSelected ? format.color : "text-gray-400"}`} />
<Icon className="w-6 h-6" />
</div>
<div className="flex-1">
<div className="flex items-center justify-between mb-1">
<h4 className={`font-semibold ${isSelected ? format.color : "text-gray-900"}`}>
<h4 className="font-bold uppercase text-black">
{format.label}
</h4>
{isSelected && (
<div className={`w-5 h-5 rounded-full ${format.bgColor} flex items-center justify-center`}>
<div className={`w-2.5 h-2.5 rounded-full ${format.color.replace("text-", "bg-")}`} />
</div>
<div className="w-4 h-4 bg-black border-2 border-black" />
)}
</div>
<p className="text-sm text-gray-600">{format.description}</p>
<p className="text-xs text-gray-600 font-bold">{format.description}</p>
</div>
</Label>
</div>
@ -139,32 +137,32 @@ export default function ExportReportDialog({
</RadioGroup>
{/* 报告预览信息 */}
<div className="mt-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h4 className="font-medium text-gray-900 mb-3"></h4>
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="flex items-center justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-gray-900">{task.project?.name || "未知"}</span>
<div className="mt-6 p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<h4 className="font-bold text-black uppercase mb-3 font-display border-b-2 border-black pb-2 w-fit"></h4>
<div className="grid grid-cols-2 gap-3 text-xs font-mono">
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<span className="text-gray-600 font-bold">:</span>
<span className="font-bold text-black">{task.project?.name || "未知"}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-gray-900">{task.quality_score.toFixed(1)}/100</span>
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<span className="text-gray-600 font-bold">:</span>
<span className="font-bold text-black">{task.quality_score.toFixed(1)}/100</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-gray-900">{task.scanned_files}/{task.total_files}</span>
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<span className="text-gray-600 font-bold">:</span>
<span className="font-bold text-black">{task.scanned_files}/{task.total_files}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-orange-600">{issues.length}</span>
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<span className="text-gray-600 font-bold">:</span>
<span className="font-bold text-orange-600">{issues.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-gray-900">{task.total_lines.toLocaleString()}</span>
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<span className="text-gray-600 font-bold">:</span>
<span className="font-bold text-black">{task.total_lines.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-red-600">
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
<span className="text-gray-600 font-bold">:</span>
<span className="font-bold text-red-600">
{issues.filter(i => i.severity === "critical").length}
</span>
</div>
@ -172,18 +170,19 @@ export default function ExportReportDialog({
</div>
</div>
<DialogFooter>
<DialogFooter className="p-6 border-t-2 border-black bg-gray-50 flex justify-end gap-3">
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isExporting}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase"
>
</Button>
<Button
onClick={handleExport}
disabled={isExporting}
className="bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800"
className="retro-btn bg-primary text-white border-2 border-black hover:bg-primary/90 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
>
{isExporting ? (
<>

View File

@ -1,11 +1,9 @@
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import {
Settings,
@ -310,8 +308,8 @@ export function SystemConfig() {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
<div className="animate-spin rounded-none h-12 w-12 border-4 border-black border-t-transparent mx-auto mb-4"></div>
<p className="text-black font-mono font-bold uppercase">...</p>
</div>
</div>
);
@ -320,98 +318,98 @@ export function SystemConfig() {
return (
<div className="space-y-6">
{/* 配置状态提示 */}
<Alert>
<Info className="h-4 w-4" />
<AlertDescription className="flex items-center justify-between">
<div>
<strong></strong>
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
<div className="flex-1 flex items-center justify-between">
<div className="font-mono text-sm text-blue-800">
<strong className="uppercase"></strong>
{configSource === 'runtime' ? (
<Badge variant="default" className="ml-2"></Badge>
<Badge variant="default" className="ml-2 rounded-none border-blue-800 bg-blue-600 text-white font-bold uppercase"></Badge>
) : (
<Badge variant="outline" className="ml-2"></Badge>
<Badge variant="outline" className="ml-2 rounded-none border-blue-800 text-blue-800 font-bold uppercase"></Badge>
)}
<span className="ml-4 text-sm">
<span className="ml-4 text-sm font-bold">
{isConfigured ? (
<span className="text-green-600 flex items-center gap-1">
<CheckCircle2 className="h-3 w-3" /> LLM
<CheckCircle2 className="h-4 w-4" /> LLM
</span>
) : (
<span className="text-orange-600 flex items-center gap-1">
<AlertCircle className="h-3 w-3" /> LLM
<AlertCircle className="h-4 w-4" /> LLM
</span>
)}
</span>
</div>
<div className="flex gap-2">
{hasChanges && (
<Button onClick={saveConfig} size="sm">
<Save className="w-4 h-4 mr-2" />
<Button onClick={saveConfig} size="sm" className="retro-btn bg-black text-white border-2 border-black hover:bg-gray-800 rounded-none h-8 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]">
<Save className="w-3 h-3 mr-2" />
</Button>
)}
{configSource === 'runtime' && (
<Button onClick={resetConfig} variant="outline" size="sm">
<RotateCcw className="w-4 h-4 mr-2" />
<Button onClick={resetConfig} variant="outline" size="sm" className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-8 font-bold uppercase">
<RotateCcw className="w-3 h-3 mr-2" />
</Button>
)}
</div>
</AlertDescription>
</Alert>
</div>
</div>
<Tabs defaultValue="llm" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="llm">
<Zap className="w-4 h-4 mr-2" />
<TabsList className="grid w-full grid-cols-4 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6">
<TabsTrigger value="llm" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
<Zap className="w-3 h-3 mr-2" />
LLM
</TabsTrigger>
<TabsTrigger value="platforms">
<Key className="w-4 h-4 mr-2" />
<TabsTrigger value="platforms" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
<Key className="w-3 h-3 mr-2" />
</TabsTrigger>
<TabsTrigger value="analysis">
<Settings className="w-4 h-4 mr-2" />
<TabsTrigger value="analysis" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
<Settings className="w-3 h-3 mr-2" />
</TabsTrigger>
<TabsTrigger value="other">
<Globe className="w-4 h-4 mr-2" />
<TabsTrigger value="other" className="rounded-none data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
<Globe className="w-3 h-3 mr-2" />
</TabsTrigger>
</TabsList>
{/* LLM 基础配置 */}
<TabsContent value="llm" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>LLM </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase">LLM </h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="space-y-2">
<Label>使 LLM </Label>
<Label className="font-bold uppercase">使 LLM </Label>
<Select
value={config.llmProvider}
onValueChange={(value) => updateConfig('llmProvider', value)}
>
<SelectTrigger>
<SelectTrigger className="retro-input h-10 bg-gray-50 border-2 border-black text-black focus:ring-0 focus:border-primary rounded-none font-mono">
<SelectValue />
</SelectTrigger>
<SelectContent>
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground"></div>
<SelectContent className="retro-card border-2 border-black rounded-none">
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase"></div>
{LLM_PROVIDERS.filter(p => p.category === 'international').map(provider => (
<SelectItem key={provider.value} value={provider.value}>
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
{provider.icon} {provider.label}
</SelectItem>
))}
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground mt-2"></div>
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase mt-2"></div>
{LLM_PROVIDERS.filter(p => p.category === 'domestic').map(provider => (
<SelectItem key={provider.value} value={provider.value}>
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
{provider.icon} {provider.label}
</SelectItem>
))}
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground mt-2"></div>
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase mt-2"></div>
{LLM_PROVIDERS.filter(p => p.category === 'local').map(provider => (
<SelectItem key={provider.value} value={provider.value}>
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
{provider.icon} {provider.label}
</SelectItem>
))}
@ -420,58 +418,62 @@ export function SystemConfig() {
</div>
<div className="space-y-2">
<Label> API Key</Label>
<Label className="font-bold uppercase"> API Key</Label>
<div className="flex gap-2">
<Input
type={showApiKeys['llm'] ? 'text' : 'password'}
value={config.llmApiKey}
onChange={(e) => updateConfig('llmApiKey', e.target.value)}
placeholder="留空则使用平台专用 API Key"
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<Button
variant="outline"
size="icon"
onClick={() => toggleShowApiKey('llm')}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
>
{showApiKeys['llm'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
使 API Key使 API Key
</p>
</div>
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<Input
value={config.llmModel}
onChange={(e) => updateConfig('llmModel', e.target.value)}
placeholder={`默认:${DEFAULT_MODELS[config.llmProvider as keyof typeof DEFAULT_MODELS] || '自动'}`}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
使
</p>
</div>
<div className="space-y-2">
<Label>API URL</Label>
<Label className="font-bold uppercase">API URL</Label>
<Input
value={config.llmBaseUrl}
onChange={(e) => updateConfig('llmBaseUrl', e.target.value)}
placeholder="例如https://api.example.com/v1"
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<div className="text-xs text-muted-foreground space-y-1">
<div className="text-xs text-gray-500 font-mono space-y-1">
<p>💡 <strong>使 API </strong>使</p>
<details className="cursor-pointer">
<summary className="text-primary hover:underline"> API </summary>
<div className="mt-2 p-3 bg-muted rounded space-y-1 text-xs">
<summary className="text-primary hover:underline font-bold"> API </summary>
<div className="mt-2 p-3 bg-gray-100 border-2 border-black rounded-none space-y-1 text-xs shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<p><strong>OpenAI </strong></p>
<p> https://your-proxy.com/v1</p>
<p> https://api.openai-proxy.org/v1</p>
<p className="pt-2"><strong></strong></p>
<p> https://your-api-gateway.com/openai</p>
<p> https://custom-endpoint.com/api</p>
<p className="pt-2 text-orange-600"> LLM </p>
<p className="pt-2 text-orange-600 font-bold"> LLM </p>
</div>
</details>
</div>
@ -479,15 +481,16 @@ export function SystemConfig() {
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<Input
type="number"
value={config.llmTimeout}
onChange={(e) => updateConfig('llmTimeout', Number(e.target.value))}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
</div>
<div className="space-y-2">
<Label>0-2</Label>
<Label className="font-bold uppercase">0-2</Label>
<Input
type="number"
step="0.1"
@ -495,278 +498,293 @@ export function SystemConfig() {
max="2"
value={config.llmTemperature}
onChange={(e) => updateConfig('llmTemperature', Number(e.target.value))}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
</div>
<div className="space-y-2">
<Label> Tokens</Label>
<Label className="font-bold uppercase"> Tokens</Label>
<Input
type="number"
value={config.llmMaxTokens}
onChange={(e) => updateConfig('llmMaxTokens', Number(e.target.value))}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
</div>
</div>
<div className="space-y-2 pt-4 border-t">
<Label></Label>
<div className="space-y-2 pt-4 border-t-2 border-black border-dashed">
<Label className="font-bold uppercase"></Label>
<Input
value={config.llmCustomHeaders}
onChange={(e) => updateConfig('llmCustomHeaders', e.target.value)}
placeholder='{"X-Custom-Header": "value", "Another-Header": "value2"}'
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<p className="text-xs text-muted-foreground">
JSON <code className="bg-muted px-1 py-0.5 rounded">&#123;"X-API-Version": "v1"&#125;</code>
<p className="text-xs text-gray-500 font-bold">
JSON <code className="bg-gray-200 px-1 py-0.5 border border-black">&#123;"X-API-Version": "v1"&#125;</code>
</p>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* 平台专用密钥 */}
<TabsContent value="platforms" className="space-y-6">
<Alert>
<Key className="h-4 w-4" />
<AlertDescription>
<div className="space-y-1">
<p> API Key便 API Key使</p>
<p className="text-xs text-muted-foreground pt-1">
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
<Key className="h-5 w-5 text-blue-600 mt-0.5" />
<div>
<div className="space-y-1 font-mono text-sm text-blue-800">
<p className="font-bold uppercase"> API Key</p>
<p>便 API Key使</p>
<p className="text-xs text-blue-700 pt-1 font-bold">
💡 <strong>使 API </strong><strong> API Key</strong> Key
LLM API URL
</p>
</div>
</AlertDescription>
</Alert>
</div>
</div>
{[
{ key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方https://makersuite.google.com/app/apikey | 或使用中转站 Key' },
{ key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方https://platform.openai.com/api-keys | 或使用中转站 Key' },
{ key: 'claudeApiKey', label: 'Claude API Key', icon: '🟣', hint: '官方https://console.anthropic.com/ | 或使用中转站 Key' },
{ key: 'qwenApiKey', label: '通义千问 API Key', icon: '🟠', hint: '官方https://dashscope.console.aliyun.com/ | 或使用中转站 Key' },
{ key: 'deepseekApiKey', label: 'DeepSeek API Key', icon: '🔷', hint: '官方https://platform.deepseek.com/ | 或使用中转站 Key' },
{ key: 'zhipuApiKey', label: '智谱AI API Key', icon: '🔴', hint: '官方https://open.bigmodel.cn/ | 或使用中转站 Key' },
{ key: 'moonshotApiKey', label: 'Moonshot API Key', icon: '🌙', hint: '官方https://platform.moonshot.cn/ | 或使用中转站 Key' },
{ key: 'baiduApiKey', label: '百度文心 API Key', icon: '🔵', hint: '官方格式API_KEY:SECRET_KEY | 或使用中转站 Key' },
{ key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方https://www.minimaxi.com/ | 或使用中转站 Key' },
{ key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方https://console.volcengine.com/ark | 或使用中转站 Key' },
].map(({ key, label, icon, hint }) => (
<Card key={key}>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<span>{icon}</span>
{label}
</CardTitle>
<CardDescription className="text-xs">{hint}</CardDescription>
</CardHeader>
<CardContent>
<div className="flex gap-2">
<Input
type={showApiKeys[key] ? 'text' : 'password'}
value={config[key as keyof SystemConfigData] as string}
onChange={(e) => updateConfig(key as keyof SystemConfigData, e.target.value)}
placeholder={`输入 ${label}`}
/>
<Button
variant="outline"
size="icon"
onClick={() => toggleShowApiKey(key)}
>
{showApiKeys[key] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[
{ key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方https://makersuite.google.com/app/apikey | 或使用中转站 Key' },
{ key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方https://platform.openai.com/api-keys | 或使用中转站 Key' },
{ key: 'claudeApiKey', label: 'Claude API Key', icon: '🟣', hint: '官方https://console.anthropic.com/ | 或使用中转站 Key' },
{ key: 'qwenApiKey', label: '通义千问 API Key', icon: '🟠', hint: '官方https://dashscope.console.aliyun.com/ | 或使用中转站 Key' },
{ key: 'deepseekApiKey', label: 'DeepSeek API Key', icon: '🔷', hint: '官方https://platform.deepseek.com/ | 或使用中转站 Key' },
{ key: 'zhipuApiKey', label: '智谱AI API Key', icon: '🔴', hint: '官方https://open.bigmodel.cn/ | 或使用中转站 Key' },
{ key: 'moonshotApiKey', label: 'Moonshot API Key', icon: '🌙', hint: '官方https://platform.moonshot.cn/ | 或使用中转站 Key' },
{ key: 'baiduApiKey', label: '百度文心 API Key', icon: '🔵', hint: '官方格式API_KEY:SECRET_KEY | 或使用中转站 Key' },
{ key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方https://www.minimaxi.com/ | 或使用中转站 Key' },
{ key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方https://console.volcengine.com/ark | 或使用中转站 Key' },
].map(({ key, label, icon, hint }) => (
<div key={key} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-sm font-display font-bold uppercase flex items-center gap-2">
<span>{icon}</span>
{label}
</h3>
<p className="text-[10px] text-gray-500 font-mono mt-1 truncate" title={hint}>{hint}</p>
</div>
</CardContent>
</Card>
))}
<div className="p-4">
<div className="flex gap-2">
<Input
type={showApiKeys[key] ? 'text' : 'password'}
value={config[key as keyof SystemConfigData] as string}
onChange={(e) => updateConfig(key as keyof SystemConfigData, e.target.value)}
placeholder={`输入 ${label}`}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono text-xs"
/>
<Button
variant="outline"
size="icon"
onClick={() => toggleShowApiKey(key)}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10 flex-shrink-0"
>
{showApiKeys[key] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
</div>
))}
</div>
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<span>🖥</span>
Ollama URL
</CardTitle>
<CardDescription className="text-xs"> Ollama API </CardDescription>
</CardHeader>
<CardContent>
</h3>
<p className="text-xs text-gray-500 font-mono mt-1"> Ollama API </p>
</div>
<div className="p-6">
<Input
value={config.ollamaBaseUrl}
onChange={(e) => updateConfig('ollamaBaseUrl', e.target.value)}
placeholder="http://localhost:11434/v1"
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* 分析参数配置 */}
<TabsContent value="analysis" className="space-y-6">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase"></h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<Input
type="number"
value={config.maxAnalyzeFiles}
onChange={(e) => updateConfig('maxAnalyzeFiles', Number(e.target.value))}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
</p>
</div>
<div className="space-y-2">
<Label>LLM </Label>
<Label className="font-bold uppercase">LLM </Label>
<Input
type="number"
value={config.llmConcurrency}
onChange={(e) => updateConfig('llmConcurrency', Number(e.target.value))}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
LLM
</p>
</div>
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<Input
type="number"
value={config.llmGapMs}
onChange={(e) => updateConfig('llmGapMs', Number(e.target.value))}
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
LLM
</p>
</div>
<div className="space-y-2">
<Label></Label>
<Label className="font-bold uppercase"></Label>
<Select
value={config.outputLanguage}
onValueChange={(value) => updateConfig('outputLanguage', value)}
>
<SelectTrigger>
<SelectTrigger className="retro-input h-10 bg-gray-50 border-2 border-black text-black focus:ring-0 focus:border-primary rounded-none font-mono">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="zh-CN">🇨🇳 </SelectItem>
<SelectItem value="en-US">🇺🇸 English</SelectItem>
<SelectContent className="retro-card border-2 border-black rounded-none">
<SelectItem value="zh-CN" className="font-mono focus:bg-primary/20 focus:text-black">🇨🇳 </SelectItem>
<SelectItem value="en-US" className="font-mono focus:bg-primary/20 focus:text-black">🇺🇸 English</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* 其他配置 */}
<TabsContent value="other" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>GitHub </CardTitle>
<CardDescription> GitHub Personal Access Token 访</CardDescription>
</CardHeader>
<CardContent>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase">GitHub </h3>
<p className="text-xs text-gray-500 font-mono mt-1"> GitHub Personal Access Token 访</p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="space-y-2">
<Label>GitHub Token</Label>
<Label className="font-bold uppercase">GitHub Token</Label>
<div className="flex gap-2">
<Input
type={showApiKeys['github'] ? 'text' : 'password'}
value={config.githubToken}
onChange={(e) => updateConfig('githubToken', e.target.value)}
placeholder="ghp_xxxxxxxxxxxx"
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<Button
variant="outline"
size="icon"
onClick={() => toggleShowApiKey('github')}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
>
{showApiKeys['github'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
https://github.com/settings/tokens
</p>
</div>
</CardContent>
</Card>
</div>
</div>
<Card>
<CardHeader>
<CardTitle>GitLab </CardTitle>
<CardDescription> GitLab Personal Access Token 访</CardDescription>
</CardHeader>
<CardContent>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase">GitLab </h3>
<p className="text-xs text-gray-500 font-mono mt-1"> GitLab Personal Access Token 访</p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="space-y-2">
<Label>GitLab Token</Label>
<Label className="font-bold uppercase">GitLab Token</Label>
<div className="flex gap-2">
<Input
type={showApiKeys['gitlab'] ? 'text' : 'password'}
value={config.gitlabToken}
onChange={(e) => updateConfig('gitlabToken', e.target.value)}
placeholder="glpat-xxxxxxxxxxxx"
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
<Button
variant="outline"
size="icon"
onClick={() => toggleShowApiKey('gitlab')}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
>
{showApiKeys['gitlab'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-gray-500 font-bold">
https://gitlab.com/-/profile/personal_access_tokens
</p>
</div>
</CardContent>
</Card>
</div>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase"></h3>
</div>
<div className="p-6 space-y-3 text-sm text-gray-600 font-mono font-medium">
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Database className="h-5 w-5 text-primary mt-0.5" />
<div>
<p className="font-medium text-foreground"></p>
<p>
<p className="font-bold text-black uppercase"></p>
<p className="text-xs mt-1">
Docker
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Settings className="h-5 w-5 text-green-600 mt-0.5" />
<div>
<p className="font-medium text-foreground"></p>
<p>
<p className="font-bold text-black uppercase"></p>
<p className="text-xs mt-1">
&gt;
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Key className="h-5 w-5 text-orange-600 mt-0.5" />
<div>
<p className="font-medium text-foreground"></p>
<p>
<p className="font-bold text-black uppercase"></p>
<p className="text-xs mt-1">
API Keys 访
</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
</Tabs>
{/* 底部操作按钮 */}
{hasChanges && (
<div className="fixed bottom-6 right-6 flex gap-3 bg-background border rounded-lg shadow-lg p-4">
<Button onClick={saveConfig} size="lg">
<div className="fixed bottom-6 right-6 flex gap-3 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 z-50">
<Button onClick={saveConfig} size="lg" className="retro-btn bg-black text-white border-2 border-black hover:bg-gray-800 rounded-none h-12 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]">
<Save className="w-4 h-4 mr-2" />
</Button>
<Button onClick={loadConfig} variant="outline" size="lg">
<Button onClick={loadConfig} variant="outline" size="lg" className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-12 font-bold uppercase">
</Button>
</div>

View File

@ -1,9 +1,7 @@
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Progress } from "@/components/ui/progress";
import {
HardDrive,
@ -97,10 +95,10 @@ export default function AdminDashboard() {
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="space-y-4 text-center">
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-primary mx-auto"></div>
<p className="text-muted-foreground">...</p>
<div className="animate-spin rounded-none h-16 w-16 border-8 border-black border-t-transparent mx-auto"></div>
<p className="text-black font-mono font-bold uppercase">...</p>
</div>
</div>
);
@ -109,17 +107,17 @@ export default function AdminDashboard() {
return (
<div className="space-y-6 pb-8">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<div className="flex items-center justify-between border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
<div>
<h1 className="text-3xl font-bold text-gray-900 flex items-center gap-3">
<Settings className="h-8 w-8 text-primary" />
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter flex items-center gap-3">
<Settings className="h-8 w-8 text-black" />
</h1>
<p className="text-gray-600 mt-2">
<p className="text-gray-600 mt-2 font-mono border-l-2 border-primary pl-2">
LLM设置使
</p>
</div>
<Button variant="outline" onClick={loadStats}>
<Button variant="outline" onClick={loadStats} className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
@ -127,12 +125,12 @@ export default function AdminDashboard() {
{/* 主要内容标签页 */}
<Tabs defaultValue="config" className="w-full">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger value="config"></TabsTrigger>
<TabsTrigger value="overview"></TabsTrigger>
<TabsTrigger value="storage"></TabsTrigger>
<TabsTrigger value="operations"></TabsTrigger>
<TabsTrigger value="settings"></TabsTrigger>
<TabsList className="grid w-full grid-cols-5 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6">
<TabsTrigger value="config" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs"></TabsTrigger>
<TabsTrigger value="overview" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs"></TabsTrigger>
<TabsTrigger value="storage" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs"></TabsTrigger>
<TabsTrigger value="operations" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs"></TabsTrigger>
<TabsTrigger value="settings" className="rounded-none data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs"></TabsTrigger>
</TabsList>
{/* 系统配置 */}
@ -144,19 +142,19 @@ export default function AdminDashboard() {
<TabsContent value="overview" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 任务完成率 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
</h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-sm font-bold">
<span></span>
<span className="font-medium">
<span>
{stats.totalTasks > 0
? Math.round((stats.completedTasks / stats.totalTasks) * 100)
: 0}%
@ -167,35 +165,36 @@ export default function AdminDashboard() {
? (stats.completedTasks / stats.totalTasks) * 100
: 0
}
className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-green-600"
/>
</div>
<div className="grid grid-cols-2 gap-4 pt-4">
<div className="grid grid-cols-2 gap-4 pt-4 border-t-2 border-black border-dashed">
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-500 uppercase font-bold"></p>
<p className="text-2xl font-bold">{stats.totalTasks}</p>
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-500 uppercase font-bold"></p>
<p className="text-2xl font-bold text-green-600">{stats.completedTasks}</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
{/* 问题解决率 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<CheckCircle2 className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
</h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-sm font-bold">
<span></span>
<span className="font-medium">
<span>
{stats.totalIssues > 0
? Math.round((stats.resolvedIssues / stats.totalIssues) * 100)
: 0}%
@ -206,104 +205,104 @@ export default function AdminDashboard() {
? (stats.resolvedIssues / stats.totalIssues) * 100
: 0
}
className="bg-orange-100"
className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-orange-500"
/>
</div>
<div className="grid grid-cols-2 gap-4 pt-4">
<div className="grid grid-cols-2 gap-4 pt-4 border-t-2 border-black border-dashed">
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-500 uppercase font-bold"></p>
<p className="text-2xl font-bold">{stats.totalIssues}</p>
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-500 uppercase font-bold"></p>
<p className="text-2xl font-bold text-green-600">{stats.resolvedIssues}</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
{/* 数据库表统计 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<Package className="h-5 w-5" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
</h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
<div className="p-6 font-mono">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div className="p-4 border rounded-lg">
<div className="p-4 border-2 border-black bg-blue-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center gap-3">
<FolderOpen className="h-8 w-8 text-primary" />
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-600 uppercase font-bold"></p>
<p className="text-2xl font-bold">{stats.totalProjects}</p>
</div>
</div>
</div>
<div className="p-4 border rounded-lg">
<div className="p-4 border-2 border-black bg-green-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center gap-3">
<Clock className="h-8 w-8 text-green-600" />
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-600 uppercase font-bold"></p>
<p className="text-2xl font-bold">{stats.totalTasks}</p>
</div>
</div>
</div>
<div className="p-4 border rounded-lg">
<div className="p-4 border-2 border-black bg-orange-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center gap-3">
<AlertTriangle className="h-8 w-8 text-orange-600" />
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-xs text-gray-600 uppercase font-bold"></p>
<p className="text-2xl font-bold">{stats.totalIssues}</p>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* 存储管理 */}
<TabsContent value="storage" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<HardDrive className="h-5 w-5" />
使
</CardTitle>
<CardDescription>
</h3>
<p className="text-xs text-gray-500 font-mono mt-1">
IndexedDB 使
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
</p>
</div>
<div className="p-6 space-y-6 font-mono">
{storageDetails ? (
<>
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center justify-between text-sm font-bold">
<span>使</span>
<span className="font-medium">{storageDetails.percentage}%</span>
<span>{storageDetails.percentage}%</span>
</div>
<Progress value={storageDetails.percentage} />
<div className="flex items-center justify-between text-xs text-muted-foreground">
<Progress value={storageDetails.percentage} className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
<div className="flex items-center justify-between text-xs text-gray-500 font-bold">
<span>{stats.storageUsed} 使</span>
<span>{stats.storageQuota} </span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4">
<div className="p-4 bg-muted rounded-lg">
<p className="text-sm text-muted-foreground mb-1">使</p>
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<p className="text-xs text-gray-600 uppercase font-bold mb-1">使</p>
<p className="text-xl font-bold">{stats.storageUsed}</p>
</div>
<div className="p-4 bg-muted rounded-lg">
<p className="text-sm text-muted-foreground mb-1"></p>
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<p className="text-xs text-gray-600 uppercase font-bold mb-1"></p>
<p className="text-xl font-bold">{stats.storageQuota}</p>
</div>
<div className="p-4 bg-muted rounded-lg">
<p className="text-sm text-muted-foreground mb-1"></p>
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<p className="text-xs text-gray-600 uppercase font-bold mb-1"></p>
<p className="text-xl font-bold">
{((storageDetails.quota - storageDetails.usage) / 1024 / 1024).toFixed(2)} MB
</p>
@ -311,59 +310,65 @@ export default function AdminDashboard() {
</div>
{storageDetails.percentage > 80 && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
使 80%
</AlertDescription>
</Alert>
<div className="bg-red-50 border-2 border-red-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(239,68,68,1)]">
<AlertCircle className="h-5 w-5 text-red-600 mt-0.5" />
<div>
<p className="font-bold text-red-800 uppercase"></p>
<p className="text-sm text-red-700 font-medium">
使 80%
</p>
</div>
</div>
)}
</>
) : (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
Storage API
</AlertDescription>
</Alert>
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
<div>
<p className="font-bold text-blue-800 uppercase"></p>
<p className="text-sm text-blue-700 font-medium">
Storage API
</p>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase"></h3>
</div>
<div className="p-6 space-y-3 font-mono">
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
<div>
<p className="font-medium"></p>
<p className="text-sm text-muted-foreground">
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-600 font-medium">
JSON
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
<div>
<p className="font-medium"></p>
<p className="text-sm text-muted-foreground">
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-600 font-medium">
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
<div>
<p className="font-medium">使</p>
<p className="text-sm text-muted-foreground">
<p className="font-bold text-black uppercase text-sm">使</p>
<p className="text-xs text-gray-600 font-medium">
使
</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
{/* 数据操作 */}
@ -373,63 +378,66 @@ export default function AdminDashboard() {
{/* 设置 */}
<TabsContent value="settings" className="space-y-6">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
<strong></strong> {
dbMode === 'api' ? '后端 PostgreSQL 数据库' :
dbMode === 'local' ? '本地 IndexedDB' :
dbMode === 'supabase' ? 'Supabase 云端(已废弃)' :
'演示模式'
}
</AlertDescription>
</Alert>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase"></h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
<div>
<p className="font-bold text-blue-800 uppercase text-sm"></p>
<p className="text-sm text-blue-700 font-medium mt-1">
{
dbMode === 'api' ? '后端 PostgreSQL 数据库' :
dbMode === 'local' ? '本地 IndexedDB' :
dbMode === 'supabase' ? 'Supabase 云端(已废弃)' :
'演示模式'
}
</p>
</div>
</div>
<div className="space-y-4 pt-4">
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div>
<p className="font-medium"></p>
<p className="text-sm text-muted-foreground">
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-500 font-medium">
</p>
</div>
<Badge variant="outline"></Badge>
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs"></Badge>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div>
<p className="font-medium"></p>
<p className="text-sm text-muted-foreground">
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-500 font-medium">
</p>
</div>
<Badge variant="outline"></Badge>
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs"></Badge>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div>
<p className="font-medium"></p>
<p className="text-sm text-muted-foreground">
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-500 font-medium">
</p>
</div>
<Badge variant="outline"></Badge>
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs"></Badge>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase"></h3>
</div>
<div className="p-6 space-y-3 text-sm text-gray-600 font-mono font-medium">
<p>
使 IndexedDB
</p>
@ -440,11 +448,11 @@ export default function AdminDashboard() {
<li></li>
<li></li>
</ul>
<p className="pt-2">
<strong></strong>
<p className="pt-2 border-t-2 border-black border-dashed mt-4">
<strong className="text-black uppercase"></strong>
</p>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
</Tabs>
</div>

View File

@ -1,13 +1,13 @@
import { useState, useEffect } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import {
Activity,
AlertTriangle,
CheckCircle,
Clock,
import {
Activity,
AlertTriangle,
CheckCircle,
Clock,
Search,
FileText,
Calendar,
@ -45,7 +45,7 @@ export default function AuditTasks() {
try {
// 只获取活动任务的最新数据
const updatedData = await api.getAuditTasks();
// 使用函数式更新,确保基于最新状态
setTasks(prevTasks => {
return prevTasks.map(prevTask => {
@ -114,265 +114,254 @@ export default function AuditTasks() {
const filteredTasks = tasks.filter(task => {
const matchesSearch = task.project?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.task_type.toLowerCase().includes(searchTerm.toLowerCase());
task.task_type.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === "all" || task.status === statusFilter;
return matchesSearch && matchesStatus;
});
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div>
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
</div>
);
}
return (
<div className="space-y-6 animate-fade-in">
<div className="space-y-6 animate-fade-in font-mono">
{/* 页面标题 */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
<div>
<h1 className="page-title"></h1>
<p className="page-subtitle"></p>
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter"></h1>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2"></p>
</div>
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}>
<Plus className="w-4 h-4 mr-2" />
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-12 px-6 text-lg font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all" onClick={() => setShowCreateDialog(true)}>
<Plus className="w-5 h-5 mr-2" />
</Button>
</div>
{/* 统计卡片 */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl">{tasks.length}</p>
</div>
<div className="stat-icon from-primary to-accent">
<Activity className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-black font-mono">{tasks.length}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-primary border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Activity className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'completed').length}</p>
</div>
<div className="stat-icon from-emerald-500 to-emerald-600">
<CheckCircle className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-green-600 font-mono">{tasks.filter(t => t.status === 'completed').length}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-green-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<CheckCircle className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'running').length}</p>
</div>
<div className="stat-icon from-orange-500 to-orange-600">
<Clock className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-orange-600 font-mono">{tasks.filter(t => t.status === 'running').length}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-orange-500 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Clock className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'failed').length}</p>
</div>
<div className="stat-icon from-red-500 to-red-600">
<AlertTriangle className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-red-600 font-mono">{tasks.filter(t => t.status === 'failed').length}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-red-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<AlertTriangle className="w-5 h-5" />
</div>
</div>
</div>
</div>
{/* 搜索和筛选 */}
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="搜索项目名称或任务类型..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<div className="flex space-x-2">
<Button
variant={statusFilter === "all" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("all")}
>
</Button>
<Button
variant={statusFilter === "running" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("running")}
>
</Button>
<Button
variant={statusFilter === "completed" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("completed")}
>
</Button>
<Button
variant={statusFilter === "failed" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("failed")}
>
</Button>
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-4">
<div className="flex-1 relative w-full">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
<Input
placeholder="搜索项目名称或任务类型..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 retro-input h-10"
/>
</div>
</CardContent>
</Card>
<div className="flex space-x-2 w-full md:w-auto overflow-x-auto pb-2 md:pb-0">
<Button
variant={statusFilter === "all" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("all")}
className={`retro-btn h-10 ${statusFilter === "all" ? "bg-black text-white" : "bg-white text-black hover:bg-gray-100"}`}
>
</Button>
<Button
variant={statusFilter === "running" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("running")}
className={`retro-btn h-10 ${statusFilter === "running" ? "bg-orange-500 text-white" : "bg-white text-black hover:bg-orange-100"}`}
>
</Button>
<Button
variant={statusFilter === "completed" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("completed")}
className={`retro-btn h-10 ${statusFilter === "completed" ? "bg-green-600 text-white" : "bg-white text-black hover:bg-green-100"}`}
>
</Button>
<Button
variant={statusFilter === "failed" ? "default" : "outline"}
size="sm"
onClick={() => setStatusFilter("failed")}
className={`retro-btn h-10 ${statusFilter === "failed" ? "bg-red-600 text-white" : "bg-white text-black hover:bg-red-100"}`}
>
</Button>
</div>
</div>
</div>
{/* 任务列表 */}
{filteredTasks.length > 0 ? (
<div className="space-y-4">
{filteredTasks.map((task) => (
<Card key={task.id} className="card-modern group">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-4">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
task.status === 'running' ? 'bg-red-50 text-red-600' :
<div key={task.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between mb-6 border-b-2 border-dashed border-gray-300 pb-4">
<div className="flex items-center space-x-4">
<div className={`w-12 h-12 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${task.status === 'completed' ? 'bg-green-100 text-green-600' :
task.status === 'running' ? 'bg-orange-100 text-orange-600' :
task.status === 'failed' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'
}`}>
{getStatusIcon(task.status)}
</div>
<div>
<h3 className="font-semibold text-lg text-gray-900 group-hover:text-primary transition-colors">
{task.project?.name || '未知项目'}
</h3>
<p className="text-sm text-gray-500">
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
</p>
</div>
{getStatusIcon(task.status)}
</div>
<Badge className={getStatusColor(task.status)}>
{task.status === 'completed' ? '已完成' :
task.status === 'running' ? '运行中' :
task.status === 'failed' ? '失败' :
task.status === 'cancelled' ? '已取消' : '等待中'}
</Badge>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="text-center p-4 bg-gradient-to-br from-blue-50 to-blue-100/30 rounded-xl border border-blue-200">
<div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
<p className="text-xs text-blue-700 font-medium"></p>
</div>
<div className="text-center p-4 bg-gradient-to-br from-purple-50 to-purple-100/30 rounded-xl border border-purple-200">
<div className="text-2xl font-bold text-purple-600 mb-1">{task.total_lines.toLocaleString()}</div>
<p className="text-xs text-purple-700 font-medium"></p>
</div>
<div className="text-center p-4 bg-gradient-to-br from-orange-50 to-orange-100/30 rounded-xl border border-orange-200">
<div className="text-2xl font-bold text-orange-600 mb-1">{task.issues_count}</div>
<p className="text-xs text-orange-700 font-medium"></p>
</div>
<div className="text-center p-4 bg-gradient-to-br from-green-50 to-green-100/30 rounded-xl border border-green-200">
<div className="text-2xl font-bold text-green-600 mb-1">{task.quality_score.toFixed(1)}</div>
<p className="text-xs text-green-700 font-medium"></p>
<div>
<h3 className="font-bold text-xl text-black font-display uppercase">
{task.project?.name || '未知项目'}
</h3>
<p className="text-sm text-gray-600 font-mono font-bold">
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
</p>
</div>
</div>
<Badge className={`rounded-none border-2 border-black font-bold uppercase ${getStatusColor(task.status)}`}>
{task.status === 'completed' ? '已完成' :
task.status === 'running' ? '运行中' :
task.status === 'failed' ? '失败' :
task.status === 'cancelled' ? '已取消' : '等待中'}
</Badge>
</div>
{/* 扫描进度 */}
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700"></span>
<span className="text-sm text-gray-500">
{task.scanned_files || 0} / {task.total_files || 0}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-gradient-to-r from-primary to-accent h-2 rounded-full transition-all duration-300"
style={{ width: `${calculateTaskProgress(task.scanned_files, task.total_files)}%` }}
></div>
</div>
<div className="text-right mt-1">
<span className="text-xs text-gray-500">
{calculateTaskProgress(task.scanned_files, task.total_files)}%
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6 font-mono">
<div className="text-center p-3 bg-blue-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
<p className="text-xs text-blue-800 font-bold uppercase"></p>
</div>
<div className="text-center p-3 bg-purple-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-2xl font-bold text-purple-600 mb-1">{task.total_lines.toLocaleString()}</div>
<p className="text-xs text-purple-800 font-bold uppercase"></p>
</div>
<div className="text-center p-3 bg-orange-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-2xl font-bold text-orange-600 mb-1">{task.issues_count}</div>
<p className="text-xs text-orange-800 font-bold uppercase"></p>
</div>
<div className="text-center p-3 bg-green-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-2xl font-bold text-green-600 mb-1">{task.quality_score.toFixed(1)}</div>
<p className="text-xs text-green-800 font-bold uppercase"></p>
</div>
</div>
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div className="flex items-center space-x-6 text-sm text-gray-500">
{/* 扫描进度 */}
<div className="mb-6 font-mono">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-bold text-black uppercase"></span>
<span className="text-sm font-bold text-gray-600">
{task.scanned_files || 0} / {task.total_files || 0}
</span>
</div>
<div className="w-full bg-gray-200 h-4 border-2 border-black">
<div
className="bg-primary h-full transition-all duration-300 border-r-2 border-black"
style={{ width: `${calculateTaskProgress(task.scanned_files, task.total_files)}%` }}
></div>
</div>
<div className="text-right mt-1">
<span className="text-xs font-bold text-gray-600">
{calculateTaskProgress(task.scanned_files, task.total_files)}%
</span>
</div>
</div>
<div className="flex items-center justify-between pt-4 border-t-2 border-black bg-gray-50 -mx-6 -mb-6 p-6 mt-6">
<div className="flex items-center space-x-6 text-sm text-gray-600 font-mono font-bold">
<div className="flex items-center">
<Calendar className="w-4 h-4 mr-2" />
{formatDate(task.created_at)}
</div>
{task.completed_at && (
<div className="flex items-center">
<Calendar className="w-4 h-4 mr-2" />
{formatDate(task.created_at)}
<CheckCircle className="w-4 h-4 mr-2" />
{formatDate(task.completed_at)}
</div>
{task.completed_at && (
<div className="flex items-center">
<CheckCircle className="w-4 h-4 mr-2" />
{formatDate(task.completed_at)}
</div>
)}
</div>
<div className="flex gap-3">
<Link to={`/tasks/${task.id}`}>
<Button variant="outline" size="sm" className="btn-secondary">
<FileText className="w-4 h-4 mr-2" />
)}
</div>
<div className="flex gap-3">
<Link to={`/tasks/${task.id}`}>
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-9">
<FileText className="w-4 h-4 mr-2" />
</Button>
</Link>
{task.project && (
<Link to={`/projects/${task.project.id}`}>
<Button size="sm" className="retro-btn bg-black text-white hover:bg-gray-800 h-9">
</Button>
</Link>
{task.project && (
<Link to={`/projects/${task.project.id}`}>
<Button size="sm" className="btn-primary">
</Button>
</Link>
)}
</div>
)}
</div>
</CardContent>
</Card>
</div>
</div>
))}
</div>
) : (
<Card className="card-modern">
<CardContent className="empty-state py-16">
<div className="empty-icon">
<Activity className="w-8 h-8 text-primary" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
</h3>
<p className="text-gray-500 mb-6 max-w-md">
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
</p>
{!searchTerm && statusFilter === "all" && (
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}>
<Plus className="w-4 h-4 mr-2" />
</Button>
)}
</CardContent>
</Card>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-16 flex flex-col items-center justify-center text-center">
<div className="w-20 h-20 bg-gray-100 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<Activity className="w-10 h-10 text-gray-400" />
</div>
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
</h3>
<p className="text-gray-500 mb-8 max-w-md font-mono">
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
</p>
{!searchTerm && statusFilter === "all" && (
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-12 px-8 text-lg font-bold uppercase" onClick={() => setShowCreateDialog(true)}>
<Plus className="w-5 h-5 mr-2" />
</Button>
)}
</div>
)}
{/* 新建任务对话框 */}

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@ -11,13 +11,13 @@ import {
import {
Activity, AlertTriangle, Clock, Code,
FileText, GitBranch, Shield, TrendingUp, Zap,
BarChart3, Target, ArrowUpRight, Calendar
BarChart3, Target, ArrowUpRight, Calendar, Terminal
} from "lucide-react";
import { api, dbMode, isDemoMode } from "@/shared/config/database";
import type { Project, AuditTask, ProjectStats } from "@/shared/types";
import { Link } from "react-router-dom";
import { toast } from "sonner";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Info } from "lucide-react";
export default function Dashboard() {
@ -85,7 +85,7 @@ export default function Dashboard() {
.sort((a, b) => new Date(a.completed_at!).getTime() - new Date(b.completed_at!).getTime())
.slice(-6); // 最近6个任务
const trendData = tasksByDate.map((task, index) => ({
const trendData = tasksByDate.map((task) => ({
date: new Date(task.completed_at!).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }),
score: task.quality_score
}));
@ -152,38 +152,44 @@ export default function Dashboard() {
if (loading) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="flex items-center justify-center min-h-[60vh] font-mono">
<div className="text-center space-y-4">
<div className="relative w-16 h-16 mx-auto">
<div className="absolute inset-0 border-4 border-blue-200 rounded-full"></div>
<div className="absolute inset-0 border-4 border-blue-600 rounded-full border-t-transparent animate-spin"></div>
<div className="absolute inset-0 border-4 border-gray-200 rounded-none"></div>
<div className="absolute inset-0 border-4 border-primary rounded-none border-t-transparent animate-spin"></div>
</div>
<p className="text-gray-600">...</p>
<p className="text-gray-600 uppercase font-bold">...</p>
</div>
</div>
);
}
return (
<div className="space-y-4 animate-fade-in">
{/* Simplified Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<div className="space-y-6 p-6 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
{/* Header Section */}
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
<div>
<h1 className="page-title"></h1>
<p className="page-subtitle">
{isDemoMode && <Badge variant="outline" className="ml-2"></Badge>}
<h1 className="text-4xl font-display font-bold uppercase tracking-tighter mb-2">
<span className="text-primary">_仪表盘</span>
</h1>
<p className="text-gray-500 font-mono text-sm flex items-center gap-2">
<Terminal className="w-4 h-4" />
// 监控状态 // 代码质量概览
{isDemoMode && <Badge variant="outline" className="ml-2 border-black bg-yellow-100 text-yellow-800 rounded-none"></Badge>}
</p>
</div>
<div className="flex gap-3">
<Link to="/instant-analysis">
<Button className="btn-primary">
<Button className="retro-btn h-12">
<Zap className="w-4 h-4 mr-2" />
</Button>
</Link>
<Link to="/projects">
<Button variant="outline" className="btn-secondary">
<Button variant="outline" className="retro-btn bg-white text-black hover:bg-gray-100 h-12">
<GitBranch className="w-4 h-4 mr-2" />
</Button>
@ -193,88 +199,80 @@ export default function Dashboard() {
{/* 数据库模式提示 */}
{isDemoMode && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
<div className="relative z-10 bg-yellow-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] flex items-start gap-3">
<Info className="h-5 w-5 text-black mt-0.5" />
<div className="text-sm font-mono text-black">
使<strong></strong>
<Link to="/admin" className="ml-2 text-primary hover:underline">
<Link to="/admin" className="ml-2 text-primary font-bold hover:underline uppercase">
&gt;
</Link>
</AlertDescription>
</Alert>
</div>
</div>
)}
{/* Stats Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<Card className="stat-card group">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value">{stats?.total_projects || 0}</p>
<p className="text-xs text-gray-500 mt-1"> {stats?.active_projects || 0} </p>
</div>
<div className="stat-icon from-primary to-accent group-hover:scale-110 transition-transform">
<Code className="w-6 h-6 text-white" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 relative z-10">
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="font-mono text-xs font-bold uppercase text-gray-500"></p>
<p className="font-display text-3xl font-bold">{stats?.total_projects || 0}</p>
<p className="text-xs font-mono mt-1 border-l-2 border-primary pl-2">: {stats?.active_projects || 0}</p>
</div>
</CardContent>
</Card>
<div className="w-12 h-12 border-2 border-black bg-primary flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Code className="w-6 h-6" />
</div>
</div>
</div>
<Card className="stat-card group">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value">{stats?.total_tasks || 0}</p>
<p className="text-xs text-gray-500 mt-1"> {stats?.completed_tasks || 0} </p>
</div>
<div className="stat-icon from-emerald-500 to-emerald-600 group-hover:scale-110 transition-transform">
<Activity className="w-6 h-6 text-white" />
</div>
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="font-mono text-xs font-bold uppercase text-gray-500"></p>
<p className="font-display text-3xl font-bold">{stats?.total_tasks || 0}</p>
<p className="text-xs font-mono mt-1 border-l-2 border-green-500 pl-2">: {stats?.completed_tasks || 0}</p>
</div>
</CardContent>
</Card>
<div className="w-12 h-12 border-2 border-black bg-green-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Activity className="w-6 h-6" />
</div>
</div>
</div>
<Card className="stat-card group">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value">{stats?.total_issues || 0}</p>
<p className="text-xs text-gray-500 mt-1"> {stats?.resolved_issues || 0} </p>
</div>
<div className="stat-icon from-orange-500 to-orange-600 group-hover:scale-110 transition-transform">
<AlertTriangle className="w-6 h-6 text-white" />
</div>
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="font-mono text-xs font-bold uppercase text-gray-500"></p>
<p className="font-display text-3xl font-bold">{stats?.total_issues || 0}</p>
<p className="text-xs font-mono mt-1 border-l-2 border-orange-500 pl-2">: {stats?.resolved_issues || 0}</p>
</div>
</CardContent>
</Card>
<div className="w-12 h-12 border-2 border-black bg-orange-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<AlertTriangle className="w-6 h-6" />
</div>
</div>
</div>
<Card className="stat-card group">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value">
{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}
</p>
{stats?.avg_quality_score ? (
<div className="flex items-center text-xs text-emerald-600 font-medium mt-1">
<TrendingUp className="w-3 h-3 mr-1" />
<span></span>
</div>
) : (
<p className="text-xs text-gray-500 mt-1"></p>
)}
</div>
<div className="stat-icon from-purple-500 to-purple-600 group-hover:scale-110 transition-transform">
<Target className="w-6 h-6 text-white" />
</div>
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="font-mono text-xs font-bold uppercase text-gray-500"></p>
<p className="font-display text-3xl font-bold">
{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}
</p>
{stats?.avg_quality_score ? (
<div className="flex items-center text-xs font-mono font-bold text-emerald-600 mt-1">
<TrendingUp className="w-3 h-3 mr-1" />
<span></span>
</div>
) : (
<p className="text-xs font-mono text-gray-500 mt-1"></p>
)}
</div>
</CardContent>
</Card>
<div className="w-12 h-12 border-2 border-black bg-purple-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Target className="w-6 h-6" />
</div>
</div>
</div>
</div>
{/* Main Content - 重新设计为更紧凑的布局 */}
@ -282,60 +280,62 @@ export default function Dashboard() {
{/* 左侧主要内容区 */}
<div className="xl:col-span-3 space-y-4">
{/* 图表区域 - 使用更紧凑的网格布局 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* 图表区域 - 使用更紧凑的网格布局 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 质量趋势图 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<CardTitle className="flex items-center text-lg">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<TrendingUp className="w-5 h-5 mr-2 text-primary" />
</CardTitle>
</CardHeader>
<CardContent>
</h3>
</div>
<div>
{qualityTrendData.length > 0 ? (
<ResponsiveContainer width="100%" height={250}>
<LineChart data={qualityTrendData}>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
<XAxis dataKey="date" stroke="#6b7280" fontSize={12} />
<YAxis stroke="#6b7280" fontSize={12} domain={[0, 100]} />
<XAxis dataKey="date" stroke="#000" fontSize={12} tick={{ fontFamily: 'Space Mono' }} />
<YAxis stroke="#000" fontSize={12} domain={[0, 100]} tick={{ fontFamily: 'Space Mono' }} />
<Tooltip
contentStyle={{
backgroundColor: 'white',
border: 'none',
borderRadius: '8px',
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)'
backgroundColor: '#fff',
border: '2px solid #000',
borderRadius: '0px',
boxShadow: '4px 4px 0px 0px rgba(0,0,0,1)',
fontFamily: 'Space Mono'
}}
/>
<Line
type="monotone"
type="step"
dataKey="score"
stroke="hsl(var(--primary))"
stroke="#000"
strokeWidth={3}
dot={{ fill: 'hsl(var(--primary))', strokeWidth: 2, r: 4 }}
activeDot={{ r: 6 }}
dot={{ fill: '#fff', stroke: '#000', strokeWidth: 2, r: 4 }}
activeDot={{ r: 6, fill: '#000' }}
/>
</LineChart>
</ResponsiveContainer>
) : (
<div className="flex items-center justify-center h-[250px] text-gray-400">
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300">
<div className="text-center">
<TrendingUp className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<p className="text-sm font-mono uppercase"></p>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
{/* 问题分布图 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<CardTitle className="flex items-center text-lg">
<BarChart3 className="w-5 h-5 mr-2 text-accent" />
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<BarChart3 className="w-5 h-5 mr-2 text-black" />
</CardTitle>
</CardHeader>
<CardContent>
</h3>
</div>
<div>
{issueTypeData.length > 0 ? (
<ResponsiveContainer width="100%" height={250}>
<PieChart>
@ -343,106 +343,116 @@ export default function Dashboard() {
data={issueTypeData}
cx="50%"
cy="50%"
labelLine={false}
labelLine={true}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
stroke="#000"
strokeWidth={2}
>
{issueTypeData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
{issueTypeData.map((entry) => (
<Cell key={`cell-${entry.name}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '2px solid #000',
borderRadius: '0px',
boxShadow: '4px 4px 0px 0px rgba(0,0,0,1)',
fontFamily: 'Space Mono'
}}
/>
</PieChart>
</ResponsiveContainer>
) : (
<div className="flex items-center justify-center h-[250px] text-gray-400">
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300">
<div className="text-center">
<BarChart3 className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<p className="text-sm font-mono uppercase"></p>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
{/* 项目概览 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<CardTitle className="flex items-center text-lg">
{/* 项目概览 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<FileText className="w-5 h-5 mr-2 text-primary" />
</CardTitle>
</CardHeader>
<CardContent>
</h3>
</div>
<div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{recentProjects.length > 0 ? (
recentProjects.map((project) => (
<Link
key={project.id}
to={`/projects/${project.id}`}
className="block p-4 rounded-lg border border-gray-200 hover:border-primary/30 hover:bg-primary/5 transition-all group"
className="block p-4 border-2 border-black bg-gray-50 hover:bg-white hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] transition-all group"
>
<div className="flex items-start justify-between mb-2">
<h4 className="font-medium text-gray-900 group-hover:text-primary transition-colors truncate">
<h4 className="font-mono font-bold text-black group-hover:text-primary transition-colors truncate uppercase">
{project.name}
</h4>
<Badge
variant={project.is_active ? "default" : "secondary"}
className="ml-2 flex-shrink-0"
variant="outline"
className={`ml-2 flex-shrink-0 border-black rounded-none ${project.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
>
{project.is_active ? '活跃' : '暂停'}
</Badge>
</div>
<p className="text-sm text-gray-500 line-clamp-2 mb-2">
<p className="text-xs text-gray-600 font-mono line-clamp-2 mb-3 border-l-2 border-gray-300 pl-2">
{project.description || '暂无描述'}
</p>
<div className="flex items-center text-xs text-gray-400">
<div className="flex items-center text-xs text-gray-500 font-mono">
<Calendar className="w-3 h-3 mr-1" />
{new Date(project.created_at).toLocaleDateString('zh-CN')}
</div>
</Link>
))
) : (
<div className="col-span-full text-center py-8 text-gray-500">
<div className="col-span-full text-center py-8 text-gray-500 border-2 border-dashed border-black">
<Code className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<p className="text-sm font-mono uppercase"></p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
</div>
{/* 最近任务 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="flex items-center text-lg">
<Clock className="w-5 h-5 mr-2 text-emerald-600" />
</CardTitle>
<Link to="/audit-tasks">
<Button variant="ghost" size="sm" className="hover:bg-emerald-50 hover:text-emerald-700">
<ArrowUpRight className="w-3 h-3 ml-1" />
</Button>
</Link>
</div>
</CardHeader>
<CardContent>
{/* 最近任务 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4 flex items-center justify-between">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<Clock className="w-5 h-5 mr-2 text-green-600" />
</h3>
<Link to="/audit-tasks">
<Button variant="ghost" size="sm" className="hover:bg-green-50 hover:text-green-700 font-mono uppercase text-xs">
<ArrowUpRight className="w-3 h-3 ml-1" />
</Button>
</Link>
</div>
<div>
<div className="space-y-3">
{recentTasks.length > 0 ? (
recentTasks.slice(0, 6).map((task) => (
<Link
key={task.id}
to={`/tasks/${task.id}`}
className="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50 transition-colors group"
className="flex items-center justify-between p-3 border-2 border-transparent hover:border-black hover:bg-gray-50 transition-all group"
>
<div className="flex items-center space-x-3">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
task.status === 'running' ? 'bg-red-50 text-red-600' :
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${task.status === 'completed' ? 'bg-green-100 text-green-600' :
task.status === 'running' ? 'bg-blue-100 text-blue-600' :
'bg-red-100 text-red-600'
}`}>
{task.status === 'completed' ? <Activity className="w-4 h-4" /> :
@ -450,113 +460,115 @@ export default function Dashboard() {
<AlertTriangle className="w-4 h-4" />}
</div>
<div>
<p className="font-medium text-sm text-gray-900 group-hover:text-primary transition-colors">
<p className="font-mono font-bold text-sm text-black group-hover:text-primary transition-colors uppercase">
{task.project?.name || '未知项目'}
</p>
<p className="text-xs text-gray-500">
: {task.quality_score?.toFixed(1) || '0.0'}
<p className="text-xs text-gray-500 font-mono">
: <span className="font-bold text-black">{task.quality_score?.toFixed(1) || '0.0'}</span>
</p>
</div>
</div>
<Badge className={getStatusColor(task.status)}>
<Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
{task.status === 'completed' ? '完成' :
task.status === 'running' ? '运行中' : '失败'}
</Badge>
</Link>
))
) : (
<div className="text-center py-8 text-gray-500">
<div className="text-center py-8 text-gray-500 border-2 border-dashed border-black">
<Activity className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<p className="text-sm font-mono uppercase"></p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
{/* 右侧边栏 - 紧凑设计 */}
<div className="xl:col-span-1 space-y-4">
{/* 快速操作 */}
<Card className="card-modern bg-gradient-to-br from-red-50/30 via-background to-red-50/20 border border-red-100/50">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center">
<Zap className="w-5 h-5 mr-2 text-indigo-600" />
{/* 快速操作 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<Zap className="w-5 h-5 mr-2 text-primary" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
</h3>
</div>
<div className="space-y-3">
<Link to="/instant-analysis" className="block">
<Button className="w-full justify-start btn-primary">
<Button className="w-full justify-start retro-btn h-10">
<Zap className="w-4 h-4 mr-2" />
</Button>
</Link>
<Link to="/projects" className="block">
<Button variant="outline" className="w-full justify-start btn-secondary">
<Button variant="outline" className="w-full justify-start retro-btn bg-white text-black hover:bg-gray-100 h-10">
<GitBranch className="w-4 h-4 mr-2" />
</Button>
</Link>
<Link to="/audit-tasks" className="block">
<Button variant="outline" className="w-full justify-start btn-secondary">
<Button variant="outline" className="w-full justify-start retro-btn bg-white text-black hover:bg-gray-100 h-10">
<Shield className="w-4 h-4 mr-2" />
</Button>
</Link>
</CardContent>
</Card>
</div>
</div>
{/* 系统状态 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center">
<Activity className="w-5 h-5 mr-2 text-emerald-600" />
{/* 系统状态 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<Activity className="w-5 h-5 mr-2 text-green-600" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
</h3>
</div>
<div className="space-y-4 font-mono">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<Badge className={
dbMode === 'api' ? 'bg-purple-100 text-purple-700' :
dbMode === 'local' ? 'bg-blue-100 text-blue-700' :
dbMode === 'supabase' ? 'bg-green-100 text-green-700' :
'bg-gray-100 text-gray-700'
}>
<span className="text-sm text-gray-600 uppercase"></span>
<Badge className={`rounded-none border border-black ${dbMode === 'api' ? 'bg-purple-100 text-purple-700' :
dbMode === 'local' ? 'bg-blue-100 text-blue-700' :
dbMode === 'supabase' ? 'bg-green-100 text-green-700' :
'bg-gray-100 text-gray-700'
}`}>
{dbMode === 'api' ? '后端' : dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm font-medium text-gray-900">
<span className="text-sm text-gray-600 uppercase"></span>
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-yellow-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
{stats?.active_projects || 0}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm font-medium text-gray-900">
<span className="text-sm text-gray-600 uppercase"></span>
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-blue-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
{recentTasks.filter(t => t.status === 'running').length}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm font-medium text-gray-900">
<span className="text-sm text-gray-600 uppercase"></span>
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-red-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
{stats ? stats.total_issues - stats.resolved_issues : 0}
</span>
</div>
</CardContent>
</Card>
</div>
</div>
{/* 最新活动 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center">
{/* 最新活动 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<Clock className="w-5 h-5 mr-2 text-orange-600" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
</h3>
</div>
<div className="space-y-3">
{recentTasks.length > 0 ? (
recentTasks.slice(0, 3).map((task) => {
const timeAgo = (() => {
@ -573,28 +585,10 @@ export default function Dashboard() {
})();
const bgColor =
task.status === 'completed' ? 'bg-emerald-50 border-emerald-200' :
task.status === 'running' ? 'bg-blue-50 border-blue-200' :
task.status === 'failed' ? 'bg-red-50 border-red-200' :
'bg-gray-50 border-gray-200';
const textColor =
task.status === 'completed' ? 'text-emerald-900' :
task.status === 'running' ? 'text-blue-900' :
task.status === 'failed' ? 'text-red-900' :
'text-gray-900';
const descColor =
task.status === 'completed' ? 'text-emerald-700' :
task.status === 'running' ? 'text-blue-700' :
task.status === 'failed' ? 'text-red-700' :
'text-gray-700';
const timeColor =
task.status === 'completed' ? 'text-emerald-600' :
task.status === 'running' ? 'text-blue-600' :
task.status === 'failed' ? 'text-red-600' :
'text-gray-600';
task.status === 'completed' ? 'bg-green-50 border-black' :
task.status === 'running' ? 'bg-blue-50 border-black' :
task.status === 'failed' ? 'bg-red-50 border-black' :
'bg-gray-50 border-black';
const statusText =
task.status === 'completed' ? '任务完成' :
@ -606,53 +600,52 @@ export default function Dashboard() {
<Link
key={task.id}
to={`/tasks/${task.id}`}
className={`block p-3 rounded-lg border ${bgColor} hover:shadow-sm transition-shadow`}
className={`block p-3 border-2 ${bgColor} hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all`}
>
<p className={`text-sm font-medium ${textColor}`}>{statusText}</p>
<p className={`text-xs ${descColor} mt-1 line-clamp-1`}>
<p className="text-sm font-bold font-mono uppercase text-black">{statusText}</p>
<p className="text-xs text-gray-700 mt-1 line-clamp-1 font-mono">
"{task.project?.name || '未知项目'}"
{task.status === 'completed' && task.issues_count > 0 &&
` - 发现 ${task.issues_count} 个问题`
}
</p>
<p className={`text-xs ${timeColor} mt-1`}>{timeAgo}</p>
<p className="text-xs text-gray-500 mt-1 font-mono">{timeAgo}</p>
</Link>
);
})
) : (
<div className="text-center py-8 text-gray-400">
<div className="text-center py-8 text-gray-400 border-2 border-dashed border-black">
<Clock className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm"></p>
<p className="text-sm font-mono uppercase"></p>
</div>
)}
</CardContent>
</Card>
</div>
</div>
{/* 使用技巧 */}
<Card className="card-modern bg-gradient-to-br from-purple-50 to-pink-50 border border-purple-100/50">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center">
{/* 使用技巧 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="pb-3 border-b-2 border-black mb-4">
<h3 className="flex items-center text-lg font-display font-bold uppercase">
<Target className="w-5 h-5 mr-2 text-purple-600" />
使
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex items-start space-x-2">
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
<p className="text-sm text-gray-700"></p>
</div>
<div className="flex items-start space-x-2">
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
<p className="text-sm text-gray-700">使</p>
</div>
<div className="flex items-start space-x-2">
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
<p className="text-sm text-gray-700"></p>
</div>
</h3>
</div>
<div className="space-y-3 font-mono text-sm">
<div className="flex items-start space-x-2">
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
<p className="text-gray-700"></p>
</div>
</CardContent>
</Card>
<div className="flex items-start space-x-2">
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
<p className="text-gray-700">使</p>
</div>
<div className="flex items-start space-x-2">
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
<p className="text-gray-700"></p>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
@ -397,28 +397,28 @@ class UserManager {
return { task: tempTask, issues: tempIssues };
};
// 渲染问题的函数,使用紧凑样式
// 渲染问题的函数,使用复古样式
const renderIssue = (issue: any, index: number) => (
<div key={index} className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-gray-300 transition-all duration-200 group">
<div className="flex items-start justify-between mb-3">
<div key={index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 mb-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-start justify-between mb-3 pb-3 border-b-2 border-dashed border-gray-300">
<div className="flex items-start space-x-3">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
'bg-blue-100 text-blue-600'
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${issue.severity === 'critical' ? 'bg-red-500 text-white' :
issue.severity === 'high' ? 'bg-orange-500 text-white' :
issue.severity === 'medium' ? 'bg-yellow-400 text-black' :
'bg-blue-400 text-white'
}`}>
{getTypeIcon(issue.type)}
</div>
<div className="flex-1">
<h4 className="font-semibold text-base text-gray-900 mb-1 group-hover:text-gray-700 transition-colors">{issue.title}</h4>
<div className="flex items-center space-x-1 text-xs text-gray-600">
<h4 className="font-bold text-base text-black mb-1 font-mono uppercase">{issue.title}</h4>
<div className="flex items-center space-x-1 text-xs text-gray-600 font-mono">
<span>📍</span>
<span> {issue.line} </span>
{issue.column && <span> {issue.column} </span>}
</div>
</div>
</div>
<Badge className={`${getSeverityColor(issue.severity)} px-2 py-1 text-xs font-medium`}>
<Badge className={`rounded-none border-2 border-black ${getSeverityColor(issue.severity)} font-bold uppercase`}>
{issue.severity === 'critical' ? '严重' :
issue.severity === 'high' ? '高' :
issue.severity === 'medium' ? '中等' : '低'}
@ -426,46 +426,43 @@ class UserManager {
</div>
{issue.description && (
<div className="bg-white border border-gray-200 rounded-lg p-3 mb-3">
<div className="flex items-center mb-1">
<Info className="w-3 h-3 text-gray-600 mr-1" />
<span className="font-medium text-gray-800 text-xs"></span>
<div className="bg-gray-50 border-2 border-black p-3 mb-3 font-mono text-xs">
<div className="flex items-center mb-1 border-b-2 border-black pb-1 w-fit">
<Info className="w-3 h-3 text-black mr-1" />
<span className="font-bold text-black uppercase"></span>
</div>
<p className="text-gray-700 text-xs leading-relaxed">
<p className="text-gray-800 leading-relaxed mt-2">
{issue.description}
</p>
</div>
)}
{issue.code_snippet && (
<div className="bg-gray-900 rounded-lg p-3 mb-3 border border-gray-700">
<div className="flex items-center justify-between mb-2">
<div className="bg-black text-green-400 p-3 mb-3 border-2 border-gray-800 font-mono text-xs relative overflow-hidden">
<div className="absolute top-0 right-0 bg-gray-800 text-white px-2 py-0.5 text-[10px] uppercase">Code</div>
<div className="flex items-center justify-between mb-2 border-b border-gray-800 pb-1">
<div className="flex items-center space-x-1">
<div className="w-4 h-4 bg-red-600 rounded flex items-center justify-center">
<Code className="w-2 h-2 text-white" />
</div>
<span className="text-gray-300 text-xs font-medium"></span>
<Code className="w-3 h-3 text-green-500" />
<span className="text-gray-400 font-bold uppercase"></span>
</div>
<span className="text-gray-400 text-xs"> {issue.line} </span>
</div>
<div className="bg-black/40 rounded p-2">
<pre className="text-xs text-gray-100 overflow-x-auto">
<code>{issue.code_snippet}</code>
</pre>
<span className="text-gray-500">Line {issue.line}</span>
</div>
<pre className="overflow-x-auto">
<code>{issue.code_snippet}</code>
</pre>
</div>
)}
<div className="space-y-2">
<div className="space-y-3">
{issue.suggestion && (
<div className="bg-white border border-blue-200 rounded-lg p-3 shadow-sm">
<div className="bg-blue-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center mb-2">
<div className="w-5 h-5 bg-blue-600 rounded flex items-center justify-center mr-2">
<Lightbulb className="w-3 h-3 text-white" />
<div className="w-5 h-5 bg-blue-600 border-2 border-black flex items-center justify-center mr-2 text-white">
<Lightbulb className="w-3 h-3" />
</div>
<span className="font-medium text-blue-800 text-sm"></span>
<span className="font-bold text-blue-900 text-sm uppercase font-mono"></span>
</div>
<p className="text-blue-700 text-xs leading-relaxed">{issue.suggestion}</p>
<p className="text-blue-900 text-xs leading-relaxed font-mono">{issue.suggestion}</p>
</div>
)}
@ -474,44 +471,44 @@ class UserManager {
if (parsedExplanation) {
return (
<div className="bg-white border border-red-200 rounded-lg p-3 shadow-sm">
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center mb-2">
<div className="w-5 h-5 bg-red-600 rounded flex items-center justify-center mr-2">
<Zap className="w-3 h-3 text-white" />
<div className="w-5 h-5 bg-red-600 border-2 border-black flex items-center justify-center mr-2 text-white">
<Zap className="w-3 h-3" />
</div>
<span className="font-medium text-red-800 text-sm">AI </span>
<span className="font-bold text-red-900 text-sm uppercase font-mono">AI </span>
</div>
<div className="space-y-2 text-xs">
<div className="space-y-2 text-xs font-mono">
{parsedExplanation.what && (
<div className="border-l-2 border-red-600 pl-2">
<span className="font-medium text-red-700"></span>
<span className="text-gray-700 ml-1">{parsedExplanation.what}</span>
<div className="border-l-4 border-red-600 pl-2">
<span className="font-bold text-red-900 uppercase block mb-1"></span>
<span className="text-gray-800">{parsedExplanation.what}</span>
</div>
)}
{parsedExplanation.why && (
<div className="border-l-2 border-gray-600 pl-2">
<span className="font-medium text-gray-700"></span>
<span className="text-gray-700 ml-1">{parsedExplanation.why}</span>
<div className="border-l-4 border-gray-600 pl-2">
<span className="font-bold text-gray-900 uppercase block mb-1"></span>
<span className="text-gray-800">{parsedExplanation.why}</span>
</div>
)}
{parsedExplanation.how && (
<div className="border-l-2 border-black pl-2">
<span className="font-medium text-black"></span>
<span className="text-gray-700 ml-1">{parsedExplanation.how}</span>
<div className="border-l-4 border-black pl-2">
<span className="font-bold text-black uppercase block mb-1"></span>
<span className="text-gray-800">{parsedExplanation.how}</span>
</div>
)}
{parsedExplanation.learn_more && (
<div className="border-l-2 border-red-400 pl-2">
<span className="font-medium text-red-600"></span>
<div className="border-l-4 border-blue-400 pl-2">
<span className="font-bold text-blue-900 uppercase block mb-1"></span>
<a
href={parsedExplanation.learn_more}
target="_blank"
rel="noopener noreferrer"
className="text-red-600 hover:text-red-800 hover:underline ml-1"
className="text-blue-700 hover:text-blue-900 hover:underline break-all"
>
{parsedExplanation.learn_more}
</a>
@ -523,12 +520,12 @@ class UserManager {
} else {
// 如果无法解析JSON回退到原始显示方式
return (
<div className="bg-white border border-red-200 rounded-lg p-3">
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center mb-2">
<Zap className="w-4 h-4 text-red-600 mr-2" />
<span className="font-medium text-red-800 text-sm">AI </span>
<span className="font-bold text-red-900 text-sm uppercase font-mono">AI </span>
</div>
<p className="text-gray-700 text-xs leading-relaxed">{issue.ai_explanation}</p>
<p className="text-gray-800 text-xs leading-relaxed font-mono">{issue.ai_explanation}</p>
</div>
);
}
@ -538,35 +535,37 @@ class UserManager {
);
return (
<div className="space-y-6 animate-fade-in">
<div className="space-y-6 animate-fade-in font-mono">
{/* 页面标题 */}
<div>
<h1 className="page-title"></h1>
<p className="page-subtitle"></p>
<div className="border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter"></h1>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2"></p>
</div>
{/* 代码输入区域 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base"></CardTitle>
{result && (
<Button variant="outline" onClick={clearAnalysis} size="sm">
<X className="w-4 h-4 mr-2" />
</Button>
)}
</div>
</CardHeader>
<CardContent className="space-y-3">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
<h3 className="text-lg font-display font-bold uppercase flex items-center">
<Code className="w-5 h-5 mr-2" />
</h3>
{result && (
<Button variant="outline" onClick={clearAnalysis} size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-8">
<X className="w-4 h-4 mr-2" />
</Button>
)}
</div>
<div className="p-6 space-y-4">
{/* 工具栏 */}
<div className="flex flex-col sm:flex-row gap-3">
<div className="flex-1">
<Select value={language} onValueChange={setLanguage}>
<SelectTrigger className="h-9">
<SelectTrigger className="h-10 retro-input rounded-none border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] focus:ring-0">
<SelectValue placeholder="选择编程语言" />
</SelectTrigger>
<SelectContent>
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
{supportedLanguages.map((lang) => (
<SelectItem key={lang} value={lang}>
{lang.charAt(0).toUpperCase() + lang.slice(1)}
@ -579,9 +578,9 @@ class UserManager {
variant="outline"
onClick={() => fileInputRef.current?.click()}
disabled={analyzing}
size="sm"
className="retro-btn bg-white text-black hover:bg-gray-100 h-10"
>
<Upload className="w-3 h-3 mr-1" />
<Upload className="w-4 h-4 mr-2" />
</Button>
<input
@ -594,14 +593,14 @@ class UserManager {
</div>
{/* 快速示例 */}
<div className="flex flex-wrap gap-2 items-center">
<span className="text-xs text-gray-600"></span>
<div className="flex flex-wrap gap-2 items-center p-2 bg-gray-50 border-2 border-dashed border-gray-300">
<span className="text-xs font-bold uppercase text-gray-600 mr-2"></span>
<Button
variant="outline"
size="sm"
onClick={() => loadExampleCode('javascript')}
disabled={analyzing}
className="h-7 px-2 text-xs"
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-yellow-100"
>
JavaScript
</Button>
@ -610,7 +609,7 @@ class UserManager {
size="sm"
onClick={() => loadExampleCode('python')}
disabled={analyzing}
className="h-7 px-2 text-xs"
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-blue-100"
>
Python
</Button>
@ -619,7 +618,7 @@ class UserManager {
size="sm"
onClick={() => loadExampleCode('java')}
disabled={analyzing}
className="h-7 px-2 text-xs"
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-red-100"
>
Java
</Button>
@ -628,7 +627,7 @@ class UserManager {
size="sm"
onClick={() => loadExampleCode('swift')}
disabled={analyzing}
className="h-7 px-2 text-xs"
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-orange-100"
>
Swift
</Button>
@ -637,22 +636,25 @@ class UserManager {
size="sm"
onClick={() => loadExampleCode('kotlin')}
disabled={analyzing}
className="h-7 px-2 text-xs"
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-purple-100"
>
Kotlin
</Button>
</div>
{/* 代码编辑器 */}
<div>
<div className="relative">
<div className="absolute top-0 right-0 bg-black text-white px-2 py-1 text-xs font-mono uppercase z-10 border-l-2 border-b-2 border-white">
Editor
</div>
<Textarea
placeholder="粘贴代码或上传文件..."
value={code}
onChange={(e) => setCode(e.target.value)}
className="min-h-[250px] font-mono text-sm"
className="min-h-[300px] font-mono text-sm retro-input bg-gray-900 text-green-400 border-2 border-black p-4 focus:ring-0 focus:border-primary"
disabled={analyzing}
/>
<div className="text-xs text-gray-500 mt-1">
<div className="text-xs text-gray-500 mt-1 font-mono text-right">
{code.length} {code.split('\n').length}
</div>
</div>
@ -661,176 +663,174 @@ class UserManager {
<Button
onClick={handleAnalyze}
disabled={!code.trim() || !language || analyzing}
className="w-full btn-primary"
className="w-full retro-btn bg-primary text-white hover:bg-primary/90 h-12 text-lg font-bold uppercase"
>
{analyzing ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
<div className="animate-spin rounded-none h-5 w-5 border-4 border-white border-t-transparent mr-3"></div>
...
</>
) : (
<>
<Zap className="w-4 h-4 mr-2" />
<Zap className="w-5 h-5 mr-2" />
</>
)}
</Button>
</CardContent>
</Card>
</div>
</div>
{/* 分析结果区域 */}
{result && (
<div className="space-y-4">
<div className="space-y-6">
{/* 结果概览 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="flex items-center text-base">
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
</CardTitle>
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-xs">
<Clock className="w-3 h-3 mr-1" />
{analysisTime.toFixed(2)}s
</Badge>
<Badge variant="outline" className="text-xs">
{language.charAt(0).toUpperCase() + language.slice(1)}
</Badge>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
<h3 className="text-lg font-display font-bold uppercase flex items-center">
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
</h3>
<div className="flex items-center gap-2">
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
<Clock className="w-3 h-3 mr-1" />
{analysisTime.toFixed(2)}s
</Badge>
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
{language}
</Badge>
{/* 导出按钮 */}
<Button
size="sm"
onClick={() => setExportDialogOpen(true)}
className="btn-primary"
>
<Download className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 导出按钮 */}
<Button
size="sm"
onClick={() => setExportDialogOpen(true)}
className="retro-btn bg-primary text-white hover:bg-primary/90 h-8"
>
<Download className="w-4 h-4 mr-2" />
</Button>
</div>
</CardHeader>
<CardContent>
</div>
<div className="p-6">
{/* 核心指标 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="text-center p-4 bg-white rounded-lg border border-red-200">
<div className="w-12 h-12 bg-primary rounded-full flex items-center justify-center mx-auto mb-3">
<Target className="w-6 h-6 text-white" />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 font-mono">
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="w-12 h-12 bg-primary border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Target className="w-6 h-6" />
</div>
<div className="text-2xl font-bold text-primary mb-1">
<div className="text-3xl font-bold text-primary mb-1">
{result.quality_score.toFixed(1)}
</div>
<p className="text-xs font-medium text-primary/80 mb-2"></p>
<Progress value={result.quality_score} className="h-1" />
<p className="text-xs font-bold text-gray-600 uppercase mb-2"></p>
<Progress value={result.quality_score} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
</div>
<div className="text-center p-4 bg-white rounded-lg border border-red-200">
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center mx-auto mb-3">
<AlertTriangle className="w-6 h-6 text-white" />
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="w-12 h-12 bg-red-600 border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<AlertTriangle className="w-6 h-6" />
</div>
<div className="text-2xl font-bold text-red-600 mb-1">
<div className="text-3xl font-bold text-red-600 mb-1">
{result.summary.critical_issues + result.summary.high_issues}
</div>
<p className="text-xs font-medium text-red-700 mb-1"></p>
<div className="text-xs text-red-600"></div>
<p className="text-xs font-bold text-red-700 uppercase mb-1"></p>
<div className="text-xs text-red-600 font-bold uppercase"></div>
</div>
<div className="text-center p-4 bg-white rounded-lg border border-yellow-200">
<div className="w-12 h-12 bg-yellow-600 rounded-full flex items-center justify-center mx-auto mb-3">
<Info className="w-6 h-6 text-white" />
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="w-12 h-12 bg-yellow-400 border-2 border-black flex items-center justify-center mx-auto mb-3 text-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Info className="w-6 h-6" />
</div>
<div className="text-2xl font-bold text-yellow-600 mb-1">
<div className="text-3xl font-bold text-yellow-600 mb-1">
{result.summary.medium_issues + result.summary.low_issues}
</div>
<p className="text-xs font-medium text-yellow-700 mb-1"></p>
<div className="text-xs text-yellow-600"></div>
<p className="text-xs font-bold text-yellow-700 uppercase mb-1"></p>
<div className="text-xs text-yellow-600 font-bold uppercase"></div>
</div>
<div className="text-center p-4 bg-white rounded-lg border border-green-200">
<div className="w-12 h-12 bg-green-600 rounded-full flex items-center justify-center mx-auto mb-3">
<FileText className="w-6 h-6 text-white" />
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="w-12 h-12 bg-green-600 border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<FileText className="w-6 h-6" />
</div>
<div className="text-2xl font-bold text-green-600 mb-1">
<div className="text-3xl font-bold text-green-600 mb-1">
{result.issues.length}
</div>
<p className="text-xs font-medium text-green-700 mb-1"></p>
<div className="text-xs text-green-600"></div>
<p className="text-xs font-bold text-green-700 uppercase mb-1"></p>
<div className="text-xs text-green-600 font-bold uppercase"></div>
</div>
</div>
{/* 详细指标 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="text-sm font-semibold text-gray-900 mb-3 flex items-center">
<TrendingUp className="w-4 h-4 mr-1" />
<div className="bg-gray-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<h3 className="text-sm font-bold text-black uppercase mb-4 flex items-center font-mono border-b-2 border-black pb-2 w-fit">
<TrendingUp className="w-4 h-4 mr-2" />
</h3>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
<div className="text-center">
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.complexity}</div>
<p className="text-xs text-gray-600 mb-2"></p>
<Progress value={result.metrics.complexity} className="h-1" />
<div className="text-xl font-bold text-black mb-1">{result.metrics.complexity}</div>
<p className="text-xs text-gray-600 uppercase mb-2"></p>
<Progress value={result.metrics.complexity} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
</div>
<div className="text-center">
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.maintainability}</div>
<p className="text-xs text-gray-600 mb-2"></p>
<Progress value={result.metrics.maintainability} className="h-1" />
<div className="text-xl font-bold text-black mb-1">{result.metrics.maintainability}</div>
<p className="text-xs text-gray-600 uppercase mb-2"></p>
<Progress value={result.metrics.maintainability} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
</div>
<div className="text-center">
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.security}</div>
<p className="text-xs text-gray-600 mb-2"></p>
<Progress value={result.metrics.security} className="h-1" />
<div className="text-xl font-bold text-black mb-1">{result.metrics.security}</div>
<p className="text-xs text-gray-600 uppercase mb-2"></p>
<Progress value={result.metrics.security} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
</div>
<div className="text-center">
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.performance}</div>
<p className="text-xs text-gray-600 mb-2"></p>
<Progress value={result.metrics.performance} className="h-1" />
<div className="text-xl font-bold text-black mb-1">{result.metrics.performance}</div>
<p className="text-xs text-gray-600 uppercase mb-2"></p>
<Progress value={result.metrics.performance} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
{/* 问题详情 */}
<Card className="card-modern">
<CardHeader className="pb-3">
<CardTitle className="flex items-center text-base">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center">
<Shield className="w-5 h-5 mr-2 text-orange-600" />
({result.issues.length})
</CardTitle>
</CardHeader>
<CardContent>
</h3>
</div>
<div className="p-6">
{result.issues.length > 0 ? (
<Tabs defaultValue="all" className="w-full">
<TabsList className="grid w-full grid-cols-4 mb-4">
<TabsTrigger value="all" className="text-xs">
<TabsList className="grid w-full grid-cols-4 mb-6 bg-transparent border-2 border-black p-0 h-auto gap-0">
<TabsTrigger value="all" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
({result.issues.length})
</TabsTrigger>
<TabsTrigger value="critical" className="text-xs">
<TabsTrigger value="critical" className="rounded-none border-r-2 border-black data-[state=active]:bg-red-600 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
({result.issues.filter(i => i.severity === 'critical').length})
</TabsTrigger>
<TabsTrigger value="high" className="text-xs">
<TabsTrigger value="high" className="rounded-none border-r-2 border-black data-[state=active]:bg-orange-500 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
({result.issues.filter(i => i.severity === 'high').length})
</TabsTrigger>
<TabsTrigger value="medium" className="text-xs">
<TabsTrigger value="medium" className="rounded-none data-[state=active]:bg-yellow-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
({result.issues.filter(i => i.severity === 'medium').length})
</TabsTrigger>
</TabsList>
<TabsContent value="all" className="space-y-3 mt-4">
<TabsContent value="all" className="space-y-4 mt-0">
{result.issues.map((issue, index) => renderIssue(issue, index))}
</TabsContent>
{['critical', 'high', 'medium'].map(severity => (
<TabsContent key={severity} value={severity} className="space-y-3 mt-4">
<TabsContent key={severity} value={severity} className="space-y-4 mt-0">
{result.issues.filter(issue => issue.severity === severity).length > 0 ? (
result.issues.filter(issue => issue.severity === severity).map((issue, index) => renderIssue(issue, index))
) : (
<div className="text-center py-12">
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">
{severity === 'critical' ? '严重' : severity === 'high' ? '高优先级' : '中等优先级'}
</h3>
<p className="text-gray-500">
<p className="text-gray-500 font-mono">
</p>
</div>
@ -838,47 +838,45 @@ class UserManager {
</TabsContent>
))}
</Tabs>
) : (
<div className="text-center py-16">
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<div className="text-center py-16 border-2 border-dashed border-black bg-green-50">
<div className="w-20 h-20 bg-green-100 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<CheckCircle className="w-12 h-12 text-green-600" />
</div>
<h3 className="text-2xl font-bold text-green-800 mb-3"></h3>
<p className="text-green-600 text-lg mb-6"></p>
<div className="bg-green-50 rounded-lg p-6 max-w-md mx-auto">
<p className="text-green-700 text-sm">
<h3 className="text-2xl font-display font-bold text-green-800 mb-3 uppercase"></h3>
<p className="text-green-700 text-lg mb-6 font-mono font-bold"></p>
<div className="bg-white border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<p className="text-black text-sm font-mono">
</p>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
)}
{/* 分析进行中状态 */}
{analyzing && (
<Card className="card-modern">
<CardContent className="py-16">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="py-16 px-6">
<div ref={loadingCardRef} className="text-center">
<div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center mx-auto mb-6">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"></div>
<div className="w-20 h-20 bg-red-50 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="animate-spin rounded-none h-12 w-12 border-4 border-primary border-t-transparent"></div>
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-3">AI正在分析您的代码</h3>
<p className="text-gray-600 text-lg mb-6">30...</p>
<p className="text-gray-600 text-lg mb-6">使</p>
<div className="bg-red-50 rounded-lg p-6 max-w-md mx-auto">
<p className="text-red-700 text-sm">
<h3 className="text-2xl font-display font-bold text-black uppercase mb-3">AI正在分析您的代码</h3>
<p className="text-gray-600 text-lg mb-6 font-mono">30...</p>
<p className="text-gray-600 text-sm mb-6 font-mono">使</p>
<div className="bg-red-50 border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<p className="text-red-700 text-sm font-mono font-bold">
<br />
</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
)}
{/* 导出报告对话框 */}

View File

@ -5,8 +5,9 @@ import { apiClient } from '@/shared/api/serverClient';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { toast } from 'sonner';
import { Terminal, Lock, Cpu } from 'lucide-react';
export default function Login() {
const [email, setEmail] = useState('');
@ -32,64 +33,111 @@ export default function Login() {
const formData = new URLSearchParams();
formData.append('username', email);
formData.append('password', password);
const response = await apiClient.post('/auth/login', formData, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/x-www-form-urlencoded'
}
});
await login(response.data.access_token);
toast.success('登录成功');
toast.success('访问已授予');
// 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理
} catch (error: any) {
toast.error(error.response?.data?.detail || '登录失败');
toast.error(error.response?.data?.detail || '访问被拒绝');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold text-center">XCodeReviewer</CardTitle>
<CardDescription className="text-center"></CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="min-h-screen flex items-center justify-center bg-background relative overflow-hidden">
{/* Decorative Background Elements */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
<div className="absolute top-10 left-10 font-mono text-xs text-gray-400 hidden md:block">
<div>系统ID: 0x84F2</div>
<div>状态: 等待输入</div>
<div>加密: AES-256</div>
</div>
<div className="absolute bottom-10 right-10 font-mono text-xs text-gray-400 hidden md:block text-right">
<div></div>
<div>端口: 443</div>
</div>
{/* Main Card */}
<div className="w-full max-w-md relative z-10 p-4">
<div className="mb-8 text-center">
<div className="inline-flex items-center justify-center p-4 bg-primary border-2 border-black shadow-retro mb-4">
<Terminal className="w-8 h-8 text-white" />
</div>
<h1 className="text-4xl font-display font-bold tracking-tighter uppercase">
XCode<span className="text-primary">Reviewer</span>
</h1>
<p className="text-sm font-mono text-gray-500 mt-2"></p>
</div>
<div className="bg-white border-2 border-black shadow-retro p-8 relative">
{/* Decorative Corner Markers */}
<div className="absolute top-2 left-2 w-2 h-2 bg-black" />
<div className="absolute top-2 right-2 w-2 h-2 bg-black" />
<div className="absolute bottom-2 left-2 w-2 h-2 bg-black" />
<div className="absolute bottom-2 right-2 w-2 h-2 bg-black" />
<form onSubmit={handleSubmit} className="space-y-6 mt-4">
<div className="space-y-2">
<Label htmlFor="email"></Label>
<Input
id="email"
type="email"
placeholder="name@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<Label htmlFor="email" className="font-mono uppercase text-xs font-bold"> / </Label>
<div className="relative">
<Input
id="email"
type="email"
placeholder="USER@DOMAIN.COM"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="retro-input font-mono pl-10"
/>
<Cpu className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password"></Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<Label htmlFor="password" className="font-mono uppercase text-xs font-bold"></Label>
<div className="relative">
<Input
id="password"
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="retro-input font-mono pl-10"
/>
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
</div>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? '登录中...' : '登录'}
<Button
type="submit"
className="w-full retro-btn text-lg h-12 mt-4"
disabled={loading}
>
{loading ? (
<span className="flex items-center gap-2">
<span className="animate-spin">/</span> ...
</span>
) : '初始化会话'}
</Button>
</form>
</CardContent>
<CardFooter className="flex flex-col space-y-2">
<div className="text-sm text-center text-gray-500">
<span className="text-blue-600 cursor-pointer hover:underline" onClick={() => navigate('/register')}></span>
<div className="mt-6 pt-6 border-t-2 border-dashed border-gray-200 text-center">
<div className="text-xs font-mono text-gray-500">
访 <span className="text-primary font-bold cursor-pointer hover:underline" onClick={() => navigate('/register')}>访</span>
</div>
</CardFooter>
</Card>
</div>
</div>
</div>
</div>
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
@ -76,14 +76,14 @@ export default function RecycleBin() {
try {
// 删除项目数据
await api.permanentlyDeleteProject(selectedProject.id);
// 删除保存的ZIP文件如果有
try {
await deleteZipFile(selectedProject.id);
} catch (error) {
console.error('删除ZIP文件失败:', error);
}
toast.success(`项目 "${selectedProject.name}" 已永久删除`);
setShowPermanentDeleteDialog(false);
setSelectedProject(null);
@ -113,10 +113,10 @@ export default function RecycleBin() {
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-gray-500">...</p>
<div className="animate-spin rounded-none h-12 w-12 border-4 border-black border-t-transparent mx-auto mb-4"></div>
<p className="text-black font-mono font-bold uppercase">...</p>
</div>
</div>
);
@ -125,72 +125,68 @@ export default function RecycleBin() {
return (
<div className="space-y-6 animate-fade-in">
{/* 页面标题 */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
<div>
<h1 className="page-title flex items-center gap-2">
<Trash2 className="w-8 h-8 text-gray-400" />
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter flex items-center gap-2">
<Trash2 className="w-8 h-8 text-black" />
</h1>
<p className="page-subtitle"></p>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2"></p>
</div>
</div>
{/* 搜索 */}
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="搜索已删除的项目..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<div className="flex items-center space-x-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
<Input
placeholder="搜索已删除的项目..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
/>
</div>
</CardContent>
</Card>
</div>
</div>
{/* 项目列表 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredProjects.length > 0 ? (
filteredProjects.map((project) => (
<Card key={project.id} className="card-modern group opacity-75 hover:opacity-100 transition-opacity">
<CardHeader className="pb-4">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-gray-400 to-gray-500 flex items-center justify-center text-white text-lg">
{getRepositoryIcon(project.repository_type)}
</div>
<div>
<CardTitle className="text-lg">
{project.name}
</CardTitle>
{project.description && (
<p className="text-sm text-gray-500 mt-1 line-clamp-2">
{project.description}
</p>
)}
</div>
<div key={project.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all group">
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-start justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 border-2 border-black bg-white flex items-center justify-center text-black text-lg shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
{getRepositoryIcon(project.repository_type)}
</div>
<div>
<h3 className="text-lg font-bold font-display uppercase truncate max-w-[150px]">
{project.name}
</h3>
{project.description && (
<p className="text-xs text-gray-500 mt-1 line-clamp-1 font-mono">
{project.description}
</p>
)}
</div>
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700">
</Badge>
</div>
</CardHeader>
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700 border-2 border-black rounded-none font-bold uppercase text-xs">
</Badge>
</div>
<CardContent className="space-y-4">
<div className="p-4 space-y-4 font-mono">
{/* 项目信息 */}
<div className="space-y-3">
{project.repository_url && (
<div className="flex items-center text-sm text-gray-500">
<div className="flex items-center text-xs text-gray-600 font-bold">
<GitBranch className="w-4 h-4 mr-2 flex-shrink-0" />
<a
href={project.repository_url}
target="_blank"
rel="noopener noreferrer"
className="hover:text-primary transition-colors flex items-center truncate"
className="hover:text-primary transition-colors flex items-center truncate hover:underline"
>
<span className="truncate">{project.repository_url.replace('https://', '')}</span>
<ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" />
@ -198,7 +194,7 @@ export default function RecycleBin() {
</div>
)}
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center justify-between text-xs text-gray-500 font-medium">
<div className="flex items-center">
<Calendar className="w-4 h-4 mr-2" />
{formatDate(project.updated_at)}
@ -214,12 +210,12 @@ export default function RecycleBin() {
{project.programming_languages && (
<div className="flex flex-wrap gap-2">
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => (
<Badge key={lang} variant="outline" className="text-xs">
<Badge key={lang} variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
{lang}
</Badge>
))}
{JSON.parse(project.programming_languages).length > 4 && (
<Badge variant="outline" className="text-xs">
<Badge variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
+{JSON.parse(project.programming_languages).length - 4}
</Badge>
)}
@ -227,11 +223,11 @@ export default function RecycleBin() {
)}
{/* 操作按钮 */}
<div className="flex gap-2 pt-2">
<div className="flex gap-2 pt-2 border-t-2 border-black mt-2">
<Button
size="sm"
variant="outline"
className="flex-1 text-green-600 hover:text-green-700 hover:bg-green-50"
className="flex-1 text-green-700 hover:text-white hover:bg-green-600 border-2 border-black rounded-none h-9 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
onClick={() => handleRestoreClick(project)}
>
<RotateCcw className="w-4 h-4 mr-2" />
@ -240,52 +236,55 @@ export default function RecycleBin() {
<Button
size="sm"
variant="outline"
className="flex-1 text-red-600 hover:text-red-700 hover:bg-red-50"
className="flex-1 text-red-700 hover:text-white hover:bg-red-600 border-2 border-black rounded-none h-9 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
onClick={() => handlePermanentDeleteClick(project)}
>
<Trash2 className="w-4 h-4 mr-2" />
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
))
) : (
<div className="col-span-full">
<Card className="card-modern">
<CardContent className="empty-state py-16">
<div className="empty-icon">
<Inbox className="w-8 h-8 text-primary" />
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="py-16 flex flex-col items-center justify-center text-center">
<div className="w-20 h-20 bg-gray-100 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<Inbox className="w-10 h-10 text-gray-400" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
{searchTerm ? '未找到匹配的项目' : '回收站为空'}
</h3>
<p className="text-gray-500 mb-6 max-w-md">
<p className="text-gray-500 font-mono max-w-md">
{searchTerm ? '尝试调整搜索条件' : '回收站中没有已删除的项目'}
</p>
</CardContent>
</Card>
</div>
</div>
</div>
)}
</div>
{/* 恢复项目确认对话框 */}
<AlertDialog open={showRestoreDialog} onOpenChange={setShowRestoreDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
<span className="font-semibold text-gray-900">"{selectedProject?.name}"</span>
<AlertDialogContent className="retro-card border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-0 bg-white max-w-md">
<AlertDialogHeader className="p-6 border-b-2 border-black bg-gray-50">
<AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2">
<RotateCcw className="w-6 h-6 text-green-600" />
</AlertDialogTitle>
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
<span className="font-bold text-black">"{selectedProject?.name}"</span>
<br />
<br />
使
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogFooter className="p-6 bg-white flex gap-3">
<AlertDialogCancel className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase"></AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmRestore}
className="bg-green-600 hover:bg-green-700 focus:ring-green-600"
className="retro-btn bg-green-600 text-white border-2 border-black hover:bg-green-700 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]"
>
</AlertDialogAction>
@ -295,19 +294,22 @@ export default function RecycleBin() {
{/* 永久删除确认对话框 */}
<AlertDialog open={showPermanentDeleteDialog} onOpenChange={setShowPermanentDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2 text-red-600">
<AlertTriangle className="w-5 h-5" />
<AlertDialogContent className="retro-card border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-0 bg-white max-w-md">
<AlertDialogHeader className="p-6 border-b-2 border-black bg-red-50">
<AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2 text-red-600">
<AlertTriangle className="w-6 h-6" />
</AlertDialogTitle>
<AlertDialogDescription>
<span className="font-semibold text-red-600"></span> <span className="font-semibold text-gray-900">"{selectedProject?.name}"</span>
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
<span className="font-bold text-red-600 uppercase"></span> <span className="font-bold text-black">"{selectedProject?.name}"</span>
<br />
<br />
<div className="bg-red-50 border border-red-200 rounded-lg p-4 my-3">
<p className="text-red-800 font-semibold mb-2"> </p>
<ul className="list-disc list-inside text-red-700 space-y-1 text-sm">
<div className="bg-red-100 border-2 border-red-500 p-4 my-3 shadow-[4px_4px_0px_0px_rgba(239,68,68,1)]">
<p className="text-red-800 font-bold mb-2 uppercase flex items-center">
<AlertTriangle className="w-4 h-4 mr-2" />
</p>
<ul className="list-disc list-inside text-red-700 space-y-1 text-xs font-bold">
<li></li>
<li></li>
<li></li>
@ -315,11 +317,11 @@ export default function RecycleBin() {
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogFooter className="p-6 bg-white flex gap-3">
<AlertDialogCancel className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase"></AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmPermanentDelete}
className="bg-red-600 hover:bg-red-700 focus:ring-red-600"
className="retro-btn bg-red-600 text-white border-2 border-black hover:bg-red-700 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]"
>
</AlertDialogAction>

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from "react";
import { useParams, Link } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
@ -21,8 +20,7 @@ import {
Code,
Lightbulb,
Info,
Zap,
X
Zap
} from "lucide-react";
import { api } from "@/shared/config/database";
import type { AuditTask, AuditIssue } from "@/shared/types";
@ -79,24 +77,24 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
const lowIssues = issues.filter(issue => issue.severity === 'low');
const renderIssue = (issue: AuditIssue, index: number) => (
<div key={issue.id || index} className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-gray-300 transition-all duration-200 group">
<div key={issue.id || index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all group">
<div className="flex items-start justify-between mb-3">
<div className="flex items-start space-x-3">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
'bg-blue-100 text-blue-600'
}`}>
<div className={`w - 10 h - 10 border - 2 border - black flex items - center justify - center shadow - [2px_2px_0px_0px_rgba(0, 0, 0, 1)] ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
'bg-blue-100 text-blue-600'
} `}>
{getTypeIcon(issue.issue_type)}
</div>
<div className="flex-1">
<h4 className="font-semibold text-base text-gray-900 mb-1 group-hover:text-gray-700 transition-colors">{issue.title}</h4>
<div className="flex items-center space-x-1 text-xs text-gray-600">
<h4 className="font-bold text-lg text-black mb-1 group-hover:text-primary transition-colors font-display uppercase">{issue.title}</h4>
<div className="flex items-center space-x-1 text-xs text-gray-600 font-mono">
<FileText className="w-3 h-3" />
<span className="font-medium">{issue.file_path}</span>
<span className="font-bold">{issue.file_path}</span>
</div>
{issue.line_number && (
<div className="flex items-center space-x-1 text-xs text-gray-500 mt-1">
<div className="flex items-center space-x-1 text-xs text-gray-500 mt-1 font-mono">
<span>📍</span>
<span> {issue.line_number} </span>
{issue.column_number && <span> {issue.column_number} </span>}
@ -104,7 +102,7 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
)}
</div>
</div>
<Badge className={`${getSeverityColor(issue.severity)} px-2 py-1 text-xs font-medium`}>
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 2 py - 1 text - xs ${getSeverityColor(issue.severity)} `}>
{issue.severity === 'critical' ? '严重' :
issue.severity === 'high' ? '高' :
issue.severity === 'medium' ? '中等' : '低'}
@ -112,48 +110,48 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
</div>
{issue.description && (
<div className="bg-white border border-gray-200 rounded-lg p-3 mb-3">
<div className="flex items-center mb-1">
<Info className="w-3 h-3 text-gray-600 mr-1" />
<span className="font-medium text-gray-800 text-xs"></span>
<div className="bg-gray-50 border-2 border-black p-3 mb-3 font-mono">
<div className="flex items-center mb-1 border-b-2 border-gray-200 pb-1">
<Info className="w-3 h-3 text-black mr-1" />
<span className="font-bold text-black text-xs uppercase"></span>
</div>
<p className="text-gray-700 text-xs leading-relaxed">
<p className="text-gray-700 text-xs leading-relaxed mt-1">
{issue.description}
</p>
</div>
)}
{issue.code_snippet && (
<div className="bg-gray-900 rounded-lg p-3 mb-3 border border-gray-700">
<div className="flex items-center justify-between mb-2">
<div className="bg-gray-900 p-3 mb-3 border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center justify-between mb-2 border-b border-gray-700 pb-1">
<div className="flex items-center space-x-1">
<div className="w-4 h-4 bg-red-600 rounded flex items-center justify-center">
<div className="w-4 h-4 bg-red-600 flex items-center justify-center">
<Code className="w-2 h-2 text-white" />
</div>
<span className="text-gray-300 text-xs font-medium"></span>
<span className="text-green-400 text-xs font-bold font-mono uppercase">CODE_SNIPPET</span>
</div>
{issue.line_number && (
<span className="text-gray-400 text-xs"> {issue.line_number} </span>
<span className="text-gray-400 text-xs font-mono">LINE: {issue.line_number}</span>
)}
</div>
<div className="bg-black/40 rounded p-2">
<pre className="text-xs text-gray-100 overflow-x-auto">
<div className="bg-black/40 p-2 border border-gray-700">
<pre className="text-xs text-green-400 font-mono overflow-x-auto">
<code>{issue.code_snippet}</code>
</pre>
</div>
</div>
)}
<div className="space-y-2">
<div className="space-y-3">
{issue.suggestion && (
<div className="bg-white border border-blue-200 rounded-lg p-3 shadow-sm">
<div className="flex items-center mb-2">
<div className="w-5 h-5 bg-blue-600 rounded flex items-center justify-center mr-2">
<Lightbulb className="w-3 h-3 text-white" />
<div className="bg-blue-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center mb-2 border-b-2 border-blue-200 pb-1">
<div className="w-5 h-5 bg-blue-600 border-2 border-black flex items-center justify-center mr-2 text-white">
<Lightbulb className="w-3 h-3" />
</div>
<span className="font-medium text-blue-800 text-sm"></span>
<span className="font-bold text-blue-800 text-sm uppercase font-display"></span>
</div>
<p className="text-blue-700 text-xs leading-relaxed">{issue.suggestion}</p>
<p className="text-blue-900 text-xs leading-relaxed font-mono font-medium">{issue.suggestion}</p>
</div>
)}
@ -162,44 +160,44 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
if (parsedExplanation) {
return (
<div className="bg-white border border-red-200 rounded-lg p-3 shadow-sm">
<div className="flex items-center mb-2">
<div className="w-5 h-5 bg-red-600 rounded flex items-center justify-center mr-2">
<Zap className="w-3 h-3 text-white" />
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center mb-2 border-b-2 border-red-200 pb-1">
<div className="w-5 h-5 bg-red-600 border-2 border-black flex items-center justify-center mr-2 text-white">
<Zap className="w-3 h-3" />
</div>
<span className="font-medium text-red-800 text-sm">AI </span>
<span className="font-bold text-red-800 text-sm uppercase font-display">AI </span>
</div>
<div className="space-y-2 text-xs">
<div className="space-y-2 text-xs font-mono">
{parsedExplanation.what && (
<div className="border-l-2 border-red-600 pl-2">
<span className="font-medium text-red-700"></span>
<span className="text-gray-700 ml-1">{parsedExplanation.what}</span>
<div className="border-l-4 border-red-600 pl-2">
<span className="font-bold text-red-700 uppercase"></span>
<span className="text-gray-800 ml-1">{parsedExplanation.what}</span>
</div>
)}
{parsedExplanation.why && (
<div className="border-l-2 border-gray-600 pl-2">
<span className="font-medium text-gray-700"></span>
<span className="text-gray-700 ml-1">{parsedExplanation.why}</span>
<div className="border-l-4 border-gray-600 pl-2">
<span className="font-bold text-gray-700 uppercase"></span>
<span className="text-gray-800 ml-1">{parsedExplanation.why}</span>
</div>
)}
{parsedExplanation.how && (
<div className="border-l-2 border-black pl-2">
<span className="font-medium text-black"></span>
<span className="text-gray-700 ml-1">{parsedExplanation.how}</span>
<div className="border-l-4 border-black pl-2">
<span className="font-bold text-black uppercase"></span>
<span className="text-gray-800 ml-1">{parsedExplanation.how}</span>
</div>
)}
{parsedExplanation.learn_more && (
<div className="border-l-2 border-red-400 pl-2">
<span className="font-medium text-red-600"></span>
<div className="border-l-4 border-blue-400 pl-2">
<span className="font-bold text-blue-600 uppercase"></span>
<a
href={parsedExplanation.learn_more}
target="_blank"
rel="noopener noreferrer"
className="text-red-600 hover:text-red-800 hover:underline ml-1"
className="text-blue-600 hover:text-blue-800 hover:underline ml-1 font-bold"
>
{parsedExplanation.learn_more}
</a>
@ -211,12 +209,12 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
} else {
// 如果无法解析JSON回退到原始显示方式
return (
<div className="bg-white border border-red-200 rounded-lg p-3">
<div className="flex items-center mb-2">
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="flex items-center mb-2 border-b-2 border-red-200 pb-1">
<Zap className="w-4 h-4 text-red-600 mr-2" />
<span className="font-medium text-red-800 text-sm">AI </span>
<span className="font-bold text-red-800 text-sm uppercase font-display">AI </span>
</div>
<p className="text-gray-700 text-xs leading-relaxed">{issue.ai_explanation}</p>
<p className="text-gray-800 text-xs leading-relaxed font-mono">{issue.ai_explanation}</p>
</div>
);
}
@ -227,14 +225,14 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
if (issues.length === 0) {
return (
<div className="text-center py-16">
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
<div className="text-center py-16 border-2 border-dashed border-black bg-green-50">
<div className="w-20 h-20 bg-green-100 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<CheckCircle className="w-12 h-12 text-green-600" />
</div>
<h3 className="text-2xl font-bold text-green-800 mb-3"></h3>
<p className="text-green-600 text-lg mb-6"></p>
<div className="bg-green-50 rounded-lg p-6 max-w-md mx-auto">
<p className="text-green-700 text-sm">
<h3 className="text-2xl font-display font-bold text-green-800 mb-3 uppercase"></h3>
<p className="text-green-700 text-lg mb-6 font-mono font-bold"></p>
<div className="bg-white border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<p className="text-black text-sm font-mono">
</p>
</div>
@ -244,72 +242,72 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
return (
<Tabs defaultValue="all" className="w-full">
<TabsList className="grid w-full grid-cols-5 mb-6">
<TabsTrigger value="all" className="text-sm">
<TabsList className="grid w-full grid-cols-5 mb-6 bg-transparent border-2 border-black p-0 h-auto gap-0">
<TabsTrigger value="all" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
({issues.length})
</TabsTrigger>
<TabsTrigger value="critical" className="text-sm">
<TabsTrigger value="critical" className="rounded-none border-r-2 border-black data-[state=active]:bg-red-600 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
({criticalIssues.length})
</TabsTrigger>
<TabsTrigger value="high" className="text-sm">
<TabsTrigger value="high" className="rounded-none border-r-2 border-black data-[state=active]:bg-orange-500 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
({highIssues.length})
</TabsTrigger>
<TabsTrigger value="medium" className="text-sm">
<TabsTrigger value="medium" className="rounded-none border-r-2 border-black data-[state=active]:bg-yellow-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
({mediumIssues.length})
</TabsTrigger>
<TabsTrigger value="low" className="text-sm">
<TabsTrigger value="low" className="rounded-none data-[state=active]:bg-blue-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
({lowIssues.length})
</TabsTrigger>
</TabsList>
<TabsContent value="all" className="space-y-4 mt-6">
<TabsContent value="all" className="space-y-4 mt-0">
{issues.map((issue, index) => renderIssue(issue, index))}
</TabsContent>
<TabsContent value="critical" className="space-y-4 mt-6">
<TabsContent value="critical" className="space-y-4 mt-0">
{criticalIssues.length > 0 ? (
criticalIssues.map((issue, index) => renderIssue(issue, index))
) : (
<div className="text-center py-12">
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-500"></p>
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono"></h3>
<p className="text-gray-500 font-mono"></p>
</div>
)}
</TabsContent>
<TabsContent value="high" className="space-y-4 mt-6">
<TabsContent value="high" className="space-y-4 mt-0">
{highIssues.length > 0 ? (
highIssues.map((issue, index) => renderIssue(issue, index))
) : (
<div className="text-center py-12">
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-500"></p>
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono"></h3>
<p className="text-gray-500 font-mono"></p>
</div>
)}
</TabsContent>
<TabsContent value="medium" className="space-y-4 mt-6">
<TabsContent value="medium" className="space-y-4 mt-0">
{mediumIssues.length > 0 ? (
mediumIssues.map((issue, index) => renderIssue(issue, index))
) : (
<div className="text-center py-12">
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-500"></p>
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono"></h3>
<p className="text-gray-500 font-mono"></p>
</div>
)}
</TabsContent>
<TabsContent value="low" className="space-y-4 mt-6">
<TabsContent value="low" className="space-y-4 mt-0">
{lowIssues.length > 0 ? (
lowIssues.map((issue, index) => renderIssue(issue, index))
) : (
<div className="text-center py-12">
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-500"></p>
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono"></h3>
<p className="text-gray-500 font-mono"></p>
</div>
)}
</TabsContent>
@ -416,32 +414,32 @@ export default function TaskDetail() {
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div>
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
</div>
);
}
if (!task) {
return (
<div className="space-y-6 animate-fade-in">
<div className="space-y-6 animate-fade-in font-mono">
<div className="flex items-center space-x-4">
<Link to="/audit-tasks">
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100">
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
</Link>
</div>
<Card className="card-modern">
<CardContent className="empty-state py-16">
<div className="empty-icon">
<AlertTriangle className="w-8 h-8 text-red-500" />
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="py-16 flex flex-col items-center justify-center text-center">
<div className="w-20 h-20 bg-red-50 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<AlertTriangle className="w-10 h-10 text-red-500" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-500">ID是否正确</p>
</CardContent>
</Card>
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display"></h3>
<p className="text-gray-500 font-mono">ID是否正确</p>
</div>
</div>
</div>
);
}
@ -450,24 +448,24 @@ export default function TaskDetail() {
const progressPercentage = calculateTaskProgress(task.scanned_files, task.total_files);
return (
<div className="space-y-6 animate-fade-in">
<div className="space-y-6 animate-fade-in font-mono">
{/* 页面标题 */}
<div className="flex items-center justify-between">
<div className="flex items-center justify-between border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
<div className="flex items-center space-x-4">
<Link to="/audit-tasks">
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10">
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
</Link>
<div>
<h1 className="page-title"></h1>
<p className="page-subtitle">{task.project?.name || '未知项目'} - </p>
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter"></h1>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">{task.project?.name || '未知项目'} - </p>
</div>
</div>
<div className="flex items-center space-x-3">
<Badge className={getStatusColor(task.status)}>
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 3 py - 1 text - sm ${getStatusColor(task.status)} `}>
{getStatusIcon(task.status)}
<span className="ml-2">
{task.status === 'completed' ? '已完成' :
@ -476,12 +474,12 @@ export default function TaskDetail() {
task.status === 'cancelled' ? '已取消' : '等待中'}
</span>
</Badge>
{/* 已完成的任务显示导出按钮 */}
{task.status === 'completed' && (
<Button
size="sm"
className="btn-primary"
<Button
size="sm"
className="retro-btn bg-primary text-white hover:bg-primary/90 h-10"
onClick={() => setExportDialogOpen(true)}
>
<Download className="w-4 h-4 mr-2" />
@ -492,101 +490,93 @@ export default function TaskDetail() {
</div>
{/* 任务概览 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl">{progressPercentage}%</p>
<Progress value={progressPercentage} className="mt-2" />
</div>
<div className="stat-icon from-primary to-accent">
<Activity className="w-5 h-5 text-white" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div className="w-full">
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-black mb-2">{progressPercentage}%</p>
<Progress value={progressPercentage} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-primary border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ml-4">
<Activity className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl text-orange-600">{task.issues_count}</p>
</div>
<div className="stat-icon from-orange-500 to-orange-600">
<Bug className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-orange-600">{task.issues_count}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-orange-500 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<Bug className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl text-primary">{task.quality_score.toFixed(1)}</p>
</div>
<div className="stat-icon from-emerald-500 to-emerald-600">
<TrendingUp className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-green-600">{task.quality_score.toFixed(1)}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-green-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<TrendingUp className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div>
<p className="stat-label"></p>
<p className="stat-value text-xl">{task.total_lines.toLocaleString()}</p>
</div>
<div className="stat-icon from-purple-500 to-purple-600">
<FileText className="w-5 h-5 text-white" />
</div>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-3xl font-bold text-purple-600">{task.total_lines.toLocaleString()}</p>
</div>
</CardContent>
</Card>
<div className="w-10 h-10 bg-purple-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<FileText className="w-5 h-5" />
</div>
</div>
</div>
</div>
{/* 任务信息 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<Card className="card-modern">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Shield className="w-5 h-5 text-primary" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center">
<Shield className="w-5 h-5 mr-2 text-primary" />
</h3>
</div>
<div className="p-6 space-y-4 font-mono">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm font-medium text-gray-500"></p>
<p className="text-base">
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-base font-bold">
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500"></p>
<p className="text-base flex items-center">
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-base font-bold flex items-center">
<GitBranch className="w-4 h-4 mr-1" />
{task.branch_name || '默认分支'}
</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500"></p>
<p className="text-base flex items-center">
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-base font-bold flex items-center">
<Calendar className="w-4 h-4 mr-1" />
{formatDate(task.created_at)}
</p>
</div>
{task.completed_at && (
<div>
<p className="text-sm font-medium text-gray-500"></p>
<p className="text-base flex items-center">
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-base font-bold flex items-center">
<CheckCircle className="w-4 h-4 mr-1" />
{formatDate(task.completed_at)}
</p>
@ -597,10 +587,10 @@ export default function TaskDetail() {
{/* 排除模式 */}
{task.exclude_patterns && (
<div>
<p className="text-sm font-medium text-gray-500 mb-2"></p>
<p className="text-xs font-bold text-gray-600 uppercase mb-2"></p>
<div className="flex flex-wrap gap-2">
{JSON.parse(task.exclude_patterns).map((pattern: string) => (
<Badge key={pattern} variant="outline" className="text-xs">
<Badge key={pattern} variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
{pattern}
</Badge>
))}
@ -611,51 +601,51 @@ export default function TaskDetail() {
{/* 扫描配置 */}
{task.scan_config && (
<div>
<p className="text-sm font-medium text-gray-500 mb-2"></p>
<div className="bg-gray-50 rounded-lg p-3">
<pre className="text-xs text-gray-600">
<p className="text-xs font-bold text-gray-600 uppercase mb-2"></p>
<div className="bg-gray-900 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<pre className="text-xs text-green-400 font-mono">
{JSON.stringify(JSON.parse(task.scan_config), null, 2)}
</pre>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
<div>
<Card className="card-modern">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<FileText className="w-5 h-5 text-primary" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center">
<FileText className="w-5 h-5 mr-2 text-primary" />
</h3>
</div>
<div className="p-6 space-y-4 font-mono">
{task.project ? (
<>
<div>
<p className="text-sm font-medium text-gray-500"></p>
<Link to={`/projects/${task.project.id}`} className="text-base text-primary hover:underline">
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<Link to={`/ projects / ${task.project.id} `} className="text-base font-bold text-primary hover:underline hover:text-primary/80">
{task.project.name}
</Link>
</div>
{task.project.description && (
<div>
<p className="text-sm font-medium text-gray-500"></p>
<p className="text-sm text-gray-600">{task.project.description}</p>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-sm text-gray-800 font-medium">{task.project.description}</p>
</div>
)}
<div>
<p className="text-sm font-medium text-gray-500"></p>
<p className="text-base">{task.project.repository_type?.toUpperCase() || 'OTHER'}</p>
<p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="text-base font-bold">{task.project.repository_type?.toUpperCase() || 'OTHER'}</p>
</div>
{task.project.programming_languages && (
<div>
<p className="text-sm font-medium text-gray-500 mb-2"></p>
<p className="text-xs font-bold text-gray-600 uppercase mb-2"></p>
<div className="flex flex-wrap gap-1">
{JSON.parse(task.project.programming_languages).map((lang: string) => (
<Badge key={lang} variant="secondary" className="text-xs">
<Badge key={lang} variant="secondary" className="text-xs rounded-none border-2 border-black bg-white text-black font-bold uppercase">
{lang}
</Badge>
))}
@ -664,26 +654,26 @@ export default function TaskDetail() {
)}
</>
) : (
<p className="text-gray-500"></p>
<p className="text-gray-500 font-bold"></p>
)}
</CardContent>
</Card>
</div>
</div>
</div>
</div>
{/* 问题列表 */}
{issues.length > 0 && (
<Card className="card-modern">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Bug className="w-6 h-6 text-orange-600" />
<span> ({issues.length})</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<div className="p-4 border-b-2 border-black bg-gray-50">
<h3 className="text-lg font-display font-bold uppercase flex items-center">
<Bug className="w-6 h-6 mr-2 text-orange-600" />
({issues.length})
</h3>
</div>
<div className="p-6">
<IssuesList issues={issues} />
</CardContent>
</Card>
</div>
</div>
)}
{/* 导出报告对话框 */}

View File

@ -1,10 +1,10 @@
import { apiClient } from "./serverClient";
import type {
Profile,
Project,
ProjectMember,
AuditTask,
AuditIssue,
import type {
Profile,
Project,
ProjectMember,
AuditTask,
AuditIssue,
InstantAnalysis,
CreateProjectForm,
CreateAuditTaskForm,
@ -14,7 +14,7 @@ import type {
// Implement the same interface as the original localDatabase.ts but using backend API
export const api = {
// ==================== Profile 相关方法 ====================
async getProfilesById(_id: string): Promise<Profile | null> {
try {
const res = await apiClient.get('/users/me');
@ -184,7 +184,7 @@ export const api = {
}
},
async createInstantAnalysis(_analysis: InstantAnalysisForm & {
async createInstantAnalysis(_analysis: InstantAnalysisForm & {
user_id: string;
analysis_result?: string;
issues_count?: number;
@ -205,6 +205,7 @@ export const api = {
completed_tasks: number;
total_issues: number;
resolved_issues: number;
avg_quality_score: number;
}> {
try {
const res = await apiClient.get('/projects/stats');
@ -216,7 +217,8 @@ export const api = {
total_tasks: 0,
completed_tasks: 0,
total_issues: 0,
resolved_issues: 0
resolved_issues: 0,
avg_quality_score: 0
};
}
},
@ -294,7 +296,7 @@ export const api = {
}> {
const formData = new FormData();
formData.append('file', file);
const res = await apiClient.post('/database/import', formData, {
headers: {
'Content-Type': 'multipart/form-data',

View File

@ -18,11 +18,13 @@ export default {
},
},
extend: {
fontFamily: {
mono: ['"Space Mono"', '"Courier New"', 'monospace'],
sans: ['"Inter"', 'system-ui', 'sans-serif'],
display: ['"Orbitron"', 'sans-serif'],
},
colors: {
border: 'hsl(var(--border))',
borderColor: {
border: 'hsl(var(--border))',
},
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
@ -55,127 +57,62 @@ export default {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
// 专业深红色调色板
crimson: {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
950: '#450a0a',
},
burgundy: {
50: '#fdf2f8',
100: '#fce7f3',
200: '#fbcfe8',
300: '#f9a8d4',
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
700: '#be185d',
800: '#9d174d',
900: '#831843',
950: '#500724',
},
success: 'hsl(var(--success))',
warning: 'hsl(var(--warning))',
info: 'hsl(var(--info))',
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))',
},
// Cassette Futurism Palette
retro: {
orange: '#FF3D00',
teal: '#00E5FF',
pink: '#FF00FF',
yellow: '#FFEA00',
dark: '#1A1A1A',
light: '#F5F7FA',
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
backgroundImage: {
'gradient-primary': 'var(--gradient-primary)',
'gradient-card': 'var(--gradient-card)',
'gradient-background': 'var(--gradient-background)',
none: '0',
},
boxShadow: {
card: 'var(--shadow-card)',
hover: 'var(--shadow-hover)',
'retro': '4px 4px 0px 0px rgba(0,0,0,1)',
'retro-hover': '2px 2px 0px 0px rgba(0,0,0,1)',
'neon': '0 0 5px theme("colors.primary.DEFAULT"), 0 0 20px theme("colors.primary.DEFAULT")',
},
keyframes: {
'accordion-down': {
from: {
height: '0',
},
to: {
height: 'var(--radix-accordion-content-height)',
},
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)',
},
to: {
height: '0',
},
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
'fade-in': {
from: {
opacity: '0',
transform: 'translateY(10px)',
},
to: {
opacity: '1',
transform: 'translateY(0)',
},
'glitch': {
'0%, 100%': { transform: 'translate(0)' },
'20%': { transform: 'translate(-2px, 2px)' },
'40%': { transform: 'translate(-2px, -2px)' },
'60%': { transform: 'translate(2px, 2px)' },
'80%': { transform: 'translate(2px, -2px)' },
},
'slide-in': {
from: {
opacity: '0',
transform: 'translateX(-20px)',
},
to: {
opacity: '1',
transform: 'translateX(0)',
},
'scanline': {
'0%': { transform: 'translateY(-100%)' },
'100%': { transform: 'translateY(100%)' },
},
'pulse-slow': {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.5' },
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'fade-in': 'fade-in 0.5s ease-out',
'slide-in': 'slide-in 0.5s ease-out',
'glitch': 'glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite',
'scanline': 'scanline 8s linear infinite',
'pulse-slow': 'pulse-slow 3s ease-in-out infinite',
},
},
},
plugins: [
require('tailwindcss-animate'),
function ({ addUtilities }) {
addUtilities(
{
'.border-t-solid': { 'border-top-style': 'solid' },
'.border-r-solid': { 'border-right-style': 'solid' },
'.border-b-solid': { 'border-bottom-style': 'solid' },
'.border-l-solid': { 'border-left-style': 'solid' },
'.border-t-dashed': { 'border-top-style': 'dashed' },
'.border-r-dashed': { 'border-right-style': 'dashed' },
'.border-b-dashed': { 'border-bottom-style': 'dashed' },
'.border-l-dashed': { 'border-left-style': 'dashed' },
'.border-t-dotted': { 'border-top-style': 'dotted' },
'.border-r-dotted': { 'border-right-style': 'dotted' },
'.border-b-dotted': { 'border-bottom-style': 'dotted' },
'.border-l-dotted': { 'border-left-style': 'dotted' },
},
['responsive']
);
},
],
};