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 { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 210 20% 98%;
--foreground: 222.2 84% 4.9%; --foreground: 220 10% 10%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 220 10% 10%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 220 10% 10%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%; --primary: 220 100% 50%;
--secondary: 210 40% 96.1%; --primary-foreground: 0 0% 100%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%; --secondary: 15 100% 50%;
--muted-foreground: 215.4 16.3% 46.9%; --secondary-foreground: 0 0% 100%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%; --muted: 210 20% 90%;
--destructive: 0 84.2% 60.2%; --muted-foreground: 220 10% 40%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%; --accent: 180 100% 40%;
--input: 214.3 31.8% 91.4%; --accent-foreground: 0 0% 100%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem; --destructive: 0 100% 50%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 0%;
--input: 0 0% 0%;
--ring: 220 100% 50%;
--radius: 0rem;
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 220 10% 10%;
--foreground: 210 40% 98%; --foreground: 210 20% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card: 220 10% 15%;
--popover: 222.2 84% 4.9%; --card-foreground: 210 20% 98%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%; --popover: 220 10% 15%;
--primary-foreground: 222.2 47.4% 11.2%; --popover-foreground: 210 20% 98%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%; --primary: 220 100% 50%;
--muted: 217.2 32.6% 17.5%; --primary-foreground: 0 0% 100%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%; --secondary: 15 100% 50%;
--accent-foreground: 210 40% 98%; --secondary-foreground: 0 0% 100%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --muted: 220 10% 20%;
--border: 217.2 32.6% 17.5%; --muted-foreground: 210 20% 60%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%; --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; @apply border-border;
} }
body { 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { import {
GitBranch, GitBranch,
@ -208,7 +207,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
console.log('✅ 任务创建成功:', taskId); console.log('✅ 任务创建成功:', taskId);
// 记录用户操作 // 记录用户操作
import('@/shared/utils/logger').then(({ logger, LogCategory }) => { import('@/shared/utils/logger').then(({ logger }) => {
logger.logUserAction('创建审计任务', { logger.logUserAction('创建审计任务', {
taskId, taskId,
projectId: project.id, projectId: project.id,
@ -299,76 +298,74 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto"> <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> <DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
<DialogTitle className="flex items-center space-x-2"> <DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
<Shield className="w-5 h-5 text-primary" /> <Shield className="w-6 h-6 text-black" />
<span></span> <span></span>
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="space-y-6"> <div className="p-6 space-y-6">
{/* 项目选择 */} {/* 项目选择 */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-base font-medium"></Label> <Label className="text-base font-bold font-mono uppercase"></Label>
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="text-xs rounded-none border-black font-mono">
{filteredProjects.length} {filteredProjects.length}
</Badge> </Badge>
</div> </div>
{/* 项目搜索 */} {/* 项目搜索 */}
<div className="relative"> <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 <Input
placeholder="搜索项目名称..." placeholder="搜索项目名称..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10" className="pl-10 retro-input h-10"
/> />
</div> </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 ? ( {loading ? (
<div className="col-span-2 flex items-center justify-center py-8"> <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> </div>
) : filteredProjects.length > 0 ? ( ) : filteredProjects.length > 0 ? (
filteredProjects.map((project) => ( filteredProjects.map((project) => (
<Card <div
key={project.id} key={project.id}
className={`cursor-pointer transition-all hover:shadow-md ${taskForm.project_id === project.id className={`cursor-pointer transition-all border-2 p-4 relative ${taskForm.project_id === project.id
? 'ring-2 ring-primary bg-primary/5' ? 'border-primary bg-blue-50 shadow-[4px_4px_0px_0px_rgba(37,99,235,1)] translate-x-[-2px] translate-y-[-2px]'
: 'hover:bg-gray-50' : '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 })} onClick={() => setTaskForm({ ...taskForm, project_id: project.id })}
> >
<CardContent className="p-4"> <div className="flex items-start justify-between">
<div className="flex items-start justify-between"> <div className="flex-1">
<div className="flex-1"> <h4 className="font-bold text-sm font-display uppercase">{project.name}</h4>
<h4 className="font-medium text-sm">{project.name}</h4> {project.description && (
{project.description && ( <p className="text-xs text-gray-600 mt-1 line-clamp-2 font-mono">
<p className="text-xs text-gray-500 mt-1 line-clamp-2"> {project.description}
{project.description} </p>
</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-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> </div>
</CardContent> {taskForm.project_id === project.id && (
</Card> <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" /> <FileText className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-sm"> <p className="text-sm">
{searchTerm ? '未找到匹配的项目' : '暂无可用项目'} {searchTerm ? '未找到匹配的项目' : '暂无可用项目'}
@ -381,133 +378,141 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 任务配置 */} {/* 任务配置 */}
{selectedProject && ( {selectedProject && (
<Tabs defaultValue="basic" className="w-full"> <Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-3"> <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"> <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" /> <GitBranch className="w-4 h-4" />
<span></span> <span></span>
</TabsTrigger> </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" /> <FileText className="w-4 h-4" />
<span></span> <span></span>
</TabsTrigger> </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" /> <Settings className="w-4 h-4" />
<span></span> <span></span>
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="basic" className="space-y-4 mt-6"> <TabsContent value="basic" className="space-y-4 mt-6 font-mono">
{/* ZIP项目文件上传 */} {/* ZIP项目文件上传 */}
{(!selectedProject.repository_url || selectedProject.repository_url.trim() === '') && ( {(!selectedProject.repository_url || selectedProject.repository_url.trim() === '') && (
<Card className="bg-amber-50 border-amber-200"> <div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<CardContent className="p-4"> <div className="space-y-3">
<div className="space-y-3"> {loadingZipFile ? (
{loadingZipFile ? ( <div className="flex items-center space-x-3 p-4 bg-blue-50 border-2 border-black">
<div className="flex items-center space-x-3 p-4 bg-blue-50 border border-blue-200 rounded-lg"> <div className="animate-spin rounded-none h-5 w-5 border-4 border-blue-600 border-t-transparent"></div>
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div> <p className="text-sm text-blue-800 font-bold">ZIP文件...</p>
<p className="text-sm text-blue-800">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> </div>
) : zipFile ? ( <Button
<div className="flex items-start space-x-3 p-4 bg-green-50 border border-green-200 rounded-lg"> size="sm"
<Info className="w-5 h-5 text-green-600 mt-0.5" /> variant="outline"
<div className="flex-1"> onClick={() => {
<p className="font-medium text-green-900 text-sm"></p> setZipFile(null);
<p className="text-xs text-green-700 mt-1"> setHasLoadedZip(false);
使ZIP文件: {zipFile.name} ( }}
{zipFile.size >= 1024 * 1024 className="retro-btn bg-white text-black h-8 text-xs"
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB` >
: zipFile.size >= 1024
? `${(zipFile.size / 1024).toFixed(2)} KB` </Button>
: `${zipFile.size} B` </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> </p>
</div> </div>
<Button
size="sm"
variant="outline"
onClick={() => {
setZipFile(null);
setHasLoadedZip(false);
}}
>
</Button>
</div> </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"> <div className="space-y-2">
<Label htmlFor="zipFile">ZIP文件</Label> <Label htmlFor="zipFile" className="font-bold uppercase">ZIP文件</Label>
<Input <Input
id="zipFile" id="zipFile"
type="file" type="file"
accept=".zip" accept=".zip"
onChange={(e) => { onChange={(e) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (file) { if (file) {
console.log('📁 选择的文件:', { console.log('📁 选择的文件:', {
name: file.name, name: file.name,
size: file.size, size: file.size,
type: file.type, type: file.type,
sizeMB: (file.size / 1024 / 1024).toFixed(2) sizeMB: (file.size / 1024 / 1024).toFixed(2)
}); });
const validation = validateZipFile(file); const validation = validateZipFile(file);
if (!validation.valid) { if (!validation.valid) {
toast.error(validation.error || "文件无效"); toast.error(validation.error || "文件无效");
e.target.value = ''; e.target.value = '';
return; 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})`);
} }
}} setZipFile(file);
className="cursor-pointer" setHasLoadedZip(true);
/>
</div> 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`;
</div>
</CardContent> toast.success(`已选择文件: ${file.name} (${sizeText})`);
</Card> }
}}
className="cursor-pointer retro-input pt-1.5"
/>
</div>
</>
)}
</div>
</div>
)} )}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="task_type"></Label> <Label htmlFor="task_type" className="font-bold uppercase"></Label>
<Select <Select
value={taskForm.task_type} value={taskForm.task_type}
onValueChange={(value: any) => setTaskForm({ ...taskForm, task_type: value })} 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 /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<SelectItem value="repository"> <SelectItem value="repository">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<GitBranch className="w-4 h-4" /> <GitBranch className="w-4 h-4" />
<span></span> <span className="font-mono"></span>
</div> </div>
</SelectItem> </SelectItem>
<SelectItem value="instant"> <SelectItem value="instant">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Zap className="w-4 h-4" /> <Zap className="w-4 h-4" />
<span></span> <span className="font-mono"></span>
</div> </div>
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
@ -516,44 +521,43 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{taskForm.task_type === "repository" && (selectedProject.repository_url) && ( {taskForm.task_type === "repository" && (selectedProject.repository_url) && (
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="branch_name"></Label> <Label htmlFor="branch_name" className="font-bold uppercase"></Label>
<Input <Input
id="branch_name" id="branch_name"
value={taskForm.branch_name || ""} value={taskForm.branch_name || ""}
onChange={(e) => setTaskForm({ ...taskForm, branch_name: e.target.value })} onChange={(e) => setTaskForm({ ...taskForm, branch_name: e.target.value })}
placeholder={selectedProject.default_branch || "main"} placeholder={selectedProject.default_branch || "main"}
className="retro-input h-10"
/> />
</div> </div>
)} )}
</div> </div>
{/* 项目信息展示 */} {/* 项目信息展示 */}
<Card className="bg-blue-50 border-blue-200"> <div className="bg-blue-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<CardContent className="p-4"> <div className="flex items-start space-x-3">
<div className="flex items-start space-x-3"> <Info className="w-5 h-5 text-blue-600 mt-0.5" />
<Info className="w-5 h-5 text-blue-600 mt-0.5" /> <div className="text-sm font-mono">
<div className="text-sm"> <p className="font-bold text-blue-900 mb-1 uppercase">{selectedProject.name}</p>
<p className="font-medium text-blue-900 mb-1">{selectedProject.name}</p> <div className="text-blue-800 space-y-1 font-bold">
<div className="text-blue-700 space-y-1"> {selectedProject.description && (
{selectedProject.description && ( <p>{selectedProject.description}</p>
<p>{selectedProject.description}</p> )}
)} <p>{selectedProject.default_branch}</p>
<p>{selectedProject.default_branch}</p> {selectedProject.programming_languages && (
{selectedProject.programming_languages && ( <p>{JSON.parse(selectedProject.programming_languages).join(', ')}</p>
<p>{JSON.parse(selectedProject.programming_languages).join(', ')}</p> )}
)}
</div>
</div> </div>
</div> </div>
</CardContent> </div>
</Card> </div>
</TabsContent> </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 className="space-y-4">
<div> <div>
<Label className="text-base font-medium"></Label> <Label className="text-base font-bold uppercase"></Label>
<p className="text-sm text-gray-500 mt-1"> <p className="text-sm text-gray-500 mt-1 font-bold">
</p> </p>
</div> </div>
@ -561,14 +565,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 常用排除模式 */} {/* 常用排除模式 */}
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
{commonExcludePatterns.map((pattern) => ( {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 <Checkbox
checked={taskForm.exclude_patterns.includes(pattern.value)} checked={taskForm.exclude_patterns.includes(pattern.value)}
onCheckedChange={() => toggleExcludePattern(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"> <div className="flex-1">
<p className="text-sm font-medium">{pattern.label}</p> <p className="text-sm font-bold uppercase">{pattern.label}</p>
<p className="text-xs text-gray-500">{pattern.description}</p> <p className="text-xs text-gray-500 font-bold">{pattern.description}</p>
</div> </div>
</div> </div>
))} ))}
@ -576,7 +581,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 自定义排除模式 */} {/* 自定义排除模式 */}
<div className="space-y-2"> <div className="space-y-2">
<Label></Label> <Label className="font-bold uppercase"></Label>
<div className="flex space-x-2"> <div className="flex space-x-2">
<Input <Input
placeholder="例如: *.tmp, test/**" placeholder="例如: *.tmp, test/**"
@ -586,6 +591,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
e.currentTarget.value = ''; e.currentTarget.value = '';
} }
}} }}
className="retro-input h-10"
/> />
<Button <Button
type="button" type="button"
@ -595,6 +601,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
addCustomPattern(input.value); addCustomPattern(input.value);
input.value = ''; input.value = '';
}} }}
className="retro-btn bg-white text-black h-10"
> >
</Button> </Button>
@ -604,13 +611,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
{/* 已选择的排除模式 */} {/* 已选择的排除模式 */}
{taskForm.exclude_patterns.length > 0 && ( {taskForm.exclude_patterns.length > 0 && (
<div className="space-y-2"> <div className="space-y-2">
<Label></Label> <Label className="font-bold uppercase"></Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{taskForm.exclude_patterns.map((pattern) => ( {taskForm.exclude_patterns.map((pattern) => (
<Badge <Badge
key={pattern} key={pattern}
variant="secondary" 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)} onClick={() => removeExcludePattern(pattern)}
> >
{pattern} × {pattern} ×
@ -622,18 +629,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
</div> </div>
</TabsContent> </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 className="space-y-6">
<div> <div>
<Label className="text-base font-medium"></Label> <Label className="text-base font-bold uppercase"></Label>
<p className="text-sm text-gray-500 mt-1"> <p className="text-sm text-gray-500 mt-1 font-bold">
</p> </p>
</div> </div>
<div className="grid grid-cols-2 gap-6"> <div className="grid grid-cols-2 gap-6">
<div className="space-y-4"> <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 <Checkbox
checked={taskForm.scan_config.include_tests} checked={taskForm.scan_config.include_tests}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
@ -642,14 +649,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
scan_config: { ...taskForm.scan_config, include_tests: !!checked } 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> <div>
<p className="text-sm font-medium"></p> <p className="text-sm font-bold uppercase"></p>
<p className="text-xs text-gray-500"> *test*, *spec* </p> <p className="text-xs text-gray-500 font-bold"> *test*, *spec* </p>
</div> </div>
</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 <Checkbox
checked={taskForm.scan_config.include_docs} checked={taskForm.scan_config.include_docs}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
@ -658,17 +666,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
scan_config: { ...taskForm.scan_config, include_docs: !!checked } 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> <div>
<p className="text-sm font-medium"></p> <p className="text-sm font-bold uppercase"></p>
<p className="text-xs text-gray-500"> README, docs </p> <p className="text-xs text-gray-500 font-bold"> README, docs </p>
</div> </div>
</div> </div>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="max_file_size"> (KB)</Label> <Label htmlFor="max_file_size" className="font-bold uppercase"> (KB)</Label>
<Input <Input
id="max_file_size" id="max_file_size"
type="number" type="number"
@ -684,11 +693,12 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
} }
min="1" min="1"
max="10240" max="10240"
className="retro-input h-10"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="analysis_depth"></Label> <Label htmlFor="analysis_depth" className="font-bold uppercase"></Label>
<Select <Select
value={taskForm.scan_config.analysis_depth} value={taskForm.scan_config.analysis_depth}
onValueChange={(value: any) => 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 /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<SelectItem value="basic"></SelectItem> <SelectItem value="basic" className="font-mono"></SelectItem>
<SelectItem value="standard"></SelectItem> <SelectItem value="standard" className="font-mono"></SelectItem>
<SelectItem value="deep"></SelectItem> <SelectItem value="deep" className="font-mono"></SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -712,39 +722,42 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
</div> </div>
{/* 分析深度说明 */} {/* 分析深度说明 */}
<Card className="bg-amber-50 border-amber-200"> <div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<CardContent className="p-4"> <div className="flex items-start space-x-3">
<div className="flex items-start space-x-3"> <AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" /> <div className="text-sm font-mono">
<div className="text-sm"> <p className="font-bold text-amber-900 mb-2 uppercase"></p>
<p className="font-medium text-amber-900 mb-2"></p> <ul className="text-amber-800 space-y-1 text-xs font-bold">
<ul className="text-amber-800 space-y-1 text-xs"> <li> <strong></strong></li>
<li> <strong></strong></li> <li> <strong></strong></li>
<li> <strong></strong></li> <li> <strong></strong></li>
<li> <strong></strong></li> </ul>
</ul>
</div>
</div> </div>
</CardContent> </div>
</Card> </div>
</div> </div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
)} )}
{/* 操作按钮 */} {/* 操作按钮 */}
<div className="flex justify-end space-x-3 pt-4 border-t"> <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}> <Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={creating}
className="retro-btn bg-white text-black h-12 px-6 font-bold uppercase"
>
</Button> </Button>
<Button <Button
onClick={handleCreateTask} onClick={handleCreateTask}
disabled={!taskForm.project_id || creating} 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 ? ( {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 { useEffect, useRef, useState } from "react";
import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog"; import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog";
import * as DialogPrimitive from "@radix-ui/react-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 { cn, calculateTaskProgress } from "@/shared/utils/utils";
import * as VisuallyHidden from "@radix-ui/react-visually-hidden"; import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
import { taskControl } from "@/shared/services/taskControl"; import { taskControl } from "@/shared/services/taskControl";
@ -112,8 +112,8 @@ export default function TerminalProgressDialog({
// 初始化日志 // 初始化日志
addLog("🚀 审计任务已启动", "info"); addLog("🚀 审计任务已启动", "info");
addLog(`<EFBFBD> 任务ID: ${taskId}`, "info"); addLog(`任务ID: ${taskId}`, "info");
addLog(`<EFBFBD> 任务类D型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info"); addLog(`任务类型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
addLog("⏳ 正在初始化审计环境...", "info"); addLog("⏳ 正在初始化审计环境...", "info");
} }
@ -383,42 +383,31 @@ export default function TerminalProgressDialog({
const getLogColor = (type: LogEntry["type"]) => { const getLogColor = (type: LogEntry["type"]) => {
switch (type) { switch (type) {
case "success": case "success":
return "text-emerald-400"; return "text-green-500";
case "error": case "error":
return "text-rose-400"; return "text-red-500";
case "warning": case "warning":
return "text-amber-400"; return "text-yellow-500";
default: 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 ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay className="bg-black/50 backdrop-blur-sm" />
<DialogPrimitive.Content <DialogPrimitive.Content
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]", "fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
"w-[90vw] aspect-[16/9]", "w-[90vw] aspect-[16/9]",
"max-w-[1600px] max-h-[900px]", "max-w-[1200px] max-h-[800px]",
"p-0 gap-0 rounded-lg overflow-hidden", "p-0 gap-0 rounded-none overflow-hidden",
"bg-gradient-to-br from-gray-900 via-red-950/30 to-gray-900 border border-red-900/50", "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=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"duration-200 shadow-2xl" "duration-200"
)} )}
onPointerDownOutside={(e) => e.preventDefault()} onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()} onInteractOutside={(e) => e.preventDefault()}
@ -432,33 +421,46 @@ export default function TerminalProgressDialog({
</VisuallyHidden.Root> </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"> <div className="flex items-center space-x-3">
<Terminal className="w-5 h-5 text-rose-400" /> <Terminal className="w-5 h-5 text-black" />
<span className="text-sm font-medium text-gray-100"></span> <span className="text-sm font-bold text-black uppercase font-display tracking-wider">TERMINAL // 审计进度监控</span>
{getStatusIcon()}
</div> </div>
<div className="flex items-center space-x-2"> <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 <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)} onClick={() => onOpenChange(false)}
title="关闭" title="关闭"
aria-label="关闭" aria-label="关闭"
/> >
<XIcon className="w-3 h-3 text-white" />
</button>
</div> </div>
</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="p-6 bg-black overflow-y-auto h-[calc(100%-100px)] font-mono text-sm relative">
<div className="space-y-2"> {/* 扫描线效果 */}
<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) => ( {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"> <div key={index} className="flex items-start space-x-3 hover:bg-white/5 px-2 py-0.5 transition-colors">
<span className="text-rose-800/70 text-xs flex-shrink-0 w-20"> <span className="text-gray-500 text-xs flex-shrink-0 w-24 font-bold">
[{log.timestamp}] [{log.timestamp}]
</span> </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} {log.message}
</span> </span>
</div> </div>
@ -466,107 +468,63 @@ export default function TerminalProgressDialog({
{/* 光标旋转闪烁效果 */} {/* 光标旋转闪烁效果 */}
{!isCompleted && !isFailed && ( {!isCompleted && !isFailed && (
<div className="flex items-center space-x-2 mt-4"> <div className="flex items-center space-x-2 mt-4 px-2">
<span className="text-rose-800/70 text-xs w-20">[{currentTime}]</span> <span className="text-gray-500 text-xs w-24 font-bold">[{currentTime}]</span>
<span className="inline-block text-rose-400 animate-spinner font-bold text-base"></span> <span className="inline-block text-green-500 animate-pulse font-bold text-base">_</span>
</div> </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 ref={logsEndRef} />
</div> </div>
</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="px-4 py-3 bg-gray-200 border-t-4 border-gray-500 flex items-center justify-between">
<div className="flex items-center justify-between text-xs"> <div className="flex items-center space-x-2">
<span className="text-gray-300"> <div className={`w-3 h-3 border-2 border-black ${isFailed ? 'bg-red-500' : isCompleted ? 'bg-green-500' : 'bg-yellow-400 animate-pulse'}`}></div>
{isCancelled ? "🛑 任务已取消,已分析的结果已保存" : <span className="text-xs font-bold text-black uppercase font-mono tracking-tight">
isCompleted ? "✅ 任务已完成,可以关闭此窗口" : {isCancelled ? "STATUS: CANCELLED // 任务已取消" :
isFailed ? "❌ 任务失败,请检查配置后重试" : isCompleted ? "STATUS: COMPLETED // 任务已完成" :
"⏳ 审计进行中,请勿关闭窗口,过程可能较慢,请耐心等待......"} isFailed ? "STATUS: FAILED // 任务失败" :
"STATUS: RUNNING // 审计进行中..."}
</span> </span>
</div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-3">
{/* 运行中显示取消按钮 */} {/* 运行中显示取消按钮 */}
{!isCompleted && !isFailed && !isCancelled && ( {!isCompleted && !isFailed && !isCancelled && (
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={handleCancel} onClick={handleCancel}
className="h-7 text-xs bg-gray-800 border-red-600 text-red-400 hover:bg-red-900 hover:text-red-200" 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" /> <XIcon className="w-3 h-3 mr-1" />
</Button> </Button>
)} )}
{/* 失败时显示查看日志按钮 */} {/* 失败时显示查看日志按钮 */}
{isFailed && ( {isFailed && (
<button <button
onClick={() => { onClick={() => {
window.open('/logs', '_blank'); 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" 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> </button>
)} )}
{/* 已完成/失败/取消显示关闭按钮 */} {/* 已完成/失败/取消显示关闭按钮 */}
{(isCompleted || isFailed || isCancelled) && ( {(isCompleted || isFailed || isCancelled) && (
<button <button
onClick={() => onOpenChange(false)} 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" 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> </button>
)} )}
</div>
</div> </div>
</div> </div>
</DialogPrimitive.Content> </DialogPrimitive.Content>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -114,265 +114,254 @@ export default function AuditTasks() {
const filteredTasks = tasks.filter(task => { const filteredTasks = tasks.filter(task => {
const matchesSearch = task.project?.name.toLowerCase().includes(searchTerm.toLowerCase()) || 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; const matchesStatus = statusFilter === "all" || task.status === statusFilter;
return matchesSearch && matchesStatus; return matchesSearch && matchesStatus;
}); });
if (loading) { if (loading) {
return ( 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="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div> <div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
</div> </div>
); );
} }
return ( 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> <div>
<h1 className="page-title"></h1> <h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter"></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>
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}> <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-4 h-4 mr-2" /> <Plus className="w-5 h-5 mr-2" />
</Button> </Button>
</div> </div>
{/* 统计卡片 */} {/* 统计卡片 */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card className="stat-card"> <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">
<CardContent className="p-5"> <div className="flex items-center justify-between">
<div className="flex items-center justify-between"> <div>
<div> <p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="stat-label"></p> <p className="text-3xl font-bold text-black font-mono">{tasks.length}</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> </div>
</CardContent> <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)]">
</Card> <Activity className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card"> <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">
<CardContent className="p-5"> <div className="flex items-center justify-between">
<div className="flex items-center justify-between"> <div>
<div> <p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="stat-label"></p> <p className="text-3xl font-bold text-green-600 font-mono">{tasks.filter(t => t.status === 'completed').length}</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> </div>
</CardContent> <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)]">
</Card> <CheckCircle className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card"> <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">
<CardContent className="p-5"> <div className="flex items-center justify-between">
<div className="flex items-center justify-between"> <div>
<div> <p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="stat-label"></p> <p className="text-3xl font-bold text-orange-600 font-mono">{tasks.filter(t => t.status === 'running').length}</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> </div>
</CardContent> <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)]">
</Card> <Clock className="w-5 h-5" />
</div>
</div>
</div>
<Card className="stat-card"> <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">
<CardContent className="p-5"> <div className="flex items-center justify-between">
<div className="flex items-center justify-between"> <div>
<div> <p className="text-xs font-bold text-gray-600 uppercase mb-1"></p>
<p className="stat-label"></p> <p className="text-3xl font-bold text-red-600 font-mono">{tasks.filter(t => t.status === 'failed').length}</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> </div>
</CardContent> <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)]">
</Card> <AlertTriangle className="w-5 h-5" />
</div>
</div>
</div>
</div> </div>
{/* 搜索和筛选 */} {/* 搜索和筛选 */}
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<CardContent className="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 items-center space-x-4"> <div className="flex-1 relative w-full">
<div className="flex-1 relative"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" /> <Input
<Input placeholder="搜索项目名称或任务类型..."
placeholder="搜索项目名称或任务类型..." value={searchTerm}
value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)}
onChange={(e) => setSearchTerm(e.target.value)} className="pl-10 retro-input h-10"
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> </div>
</CardContent> <div className="flex space-x-2 w-full md:w-auto overflow-x-auto pb-2 md:pb-0">
</Card> <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 ? ( {filteredTasks.length > 0 ? (
<div className="space-y-4"> <div className="space-y-4">
{filteredTasks.map((task) => ( {filteredTasks.map((task) => (
<Card key={task.id} className="card-modern group"> <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">
<CardContent className="p-6"> <div className="flex items-center justify-between mb-6 border-b-2 border-dashed border-gray-300 pb-4">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center space-x-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' :
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${ task.status === 'running' ? 'bg-orange-100 text-orange-600' :
task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
task.status === 'running' ? 'bg-red-50 text-red-600' :
task.status === 'failed' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600' task.status === 'failed' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'
}`}> }`}>
{getStatusIcon(task.status)} {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>
</div> </div>
<Badge className={getStatusColor(task.status)}> <div>
{task.status === 'completed' ? '已完成' : <h3 className="font-bold text-xl text-black font-display uppercase">
task.status === 'running' ? '运行中' : {task.project?.name || '未知项目'}
task.status === 'failed' ? '失败' : </h3>
task.status === 'cancelled' ? '已取消' : '等待中'} <p className="text-sm text-gray-600 font-mono font-bold">
</Badge> {task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
</div> </p>
<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> </div>
</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="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6 font-mono">
<div className="mb-6"> <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="flex items-center justify-between mb-2"> <div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
<span className="text-sm font-medium text-gray-700"></span> <p className="text-xs text-blue-800 font-bold uppercase"></p>
<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> </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"> <div className="flex items-center">
<Calendar className="w-4 h-4 mr-2" /> <CheckCircle className="w-4 h-4 mr-2" />
{formatDate(task.created_at)} {formatDate(task.completed_at)}
</div> </div>
{task.completed_at && ( )}
<div className="flex items-center"> </div>
<CheckCircle className="w-4 h-4 mr-2" />
{formatDate(task.completed_at)}
</div>
)}
</div>
<div className="flex gap-3"> <div className="flex gap-3">
<Link to={`/tasks/${task.id}`}> <Link to={`/tasks/${task.id}`}>
<Button variant="outline" size="sm" className="btn-secondary"> <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" /> <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> </Button>
</Link> </Link>
{task.project && ( )}
<Link to={`/projects/${task.project.id}`}>
<Button size="sm" className="btn-primary">
</Button>
</Link>
)}
</div>
</div> </div>
</CardContent> </div>
</Card> </div>
))} ))}
</div> </div>
) : ( ) : (
<Card className="card-modern"> <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">
<CardContent className="empty-state py-16"> <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)]">
<div className="empty-icon"> <Activity className="w-10 h-10 text-gray-400" />
<Activity className="w-8 h-8 text-primary" /> </div>
</div> <h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
<h3 className="text-lg font-medium text-gray-900 mb-2"> {searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'} </h3>
</h3> <p className="text-gray-500 mb-8 max-w-md font-mono">
<p className="text-gray-500 mb-6 max-w-md"> {searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'} </p>
</p> {!searchTerm && statusFilter === "all" && (
{!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)}>
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}> <Plus className="w-5 h-5 mr-2" />
<Plus className="w-4 h-4 mr-2" />
</Button>
</Button> )}
)} </div>
</CardContent>
</Card>
)} )}
{/* 新建任务对话框 */} {/* 新建任务对话框 */}

View File

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

View File

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

View File

@ -5,8 +5,9 @@ import { apiClient } from '@/shared/api/serverClient';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Terminal, Lock, Cpu } from 'lucide-react';
export default function Login() { export default function Login() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@ -35,61 +36,108 @@ export default function Login() {
const response = await apiClient.post('/auth/login', formData, { const response = await apiClient.post('/auth/login', formData, {
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
} }
}); });
await login(response.data.access_token); await login(response.data.access_token);
toast.success('登录成功'); toast.success('访问已授予');
// 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理 // 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理
} catch (error: any) { } catch (error: any) {
toast.error(error.response?.data?.detail || '登录失败'); toast.error(error.response?.data?.detail || '访问被拒绝');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4"> <div className="min-h-screen flex items-center justify-center bg-background relative overflow-hidden">
<Card className="w-full max-w-md"> {/* Decorative Background Elements */}
<CardHeader className="space-y-1"> <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" />
<CardTitle className="text-2xl font-bold text-center">XCodeReviewer</CardTitle>
<CardDescription className="text-center"></CardDescription> <div className="absolute top-10 left-10 font-mono text-xs text-gray-400 hidden md:block">
</CardHeader> <div>系统ID: 0x84F2</div>
<CardContent> <div>状态: 等待输入</div>
<form onSubmit={handleSubmit} className="space-y-4"> <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"> <div className="space-y-2">
<Label htmlFor="email"></Label> <Label htmlFor="email" className="font-mono uppercase text-xs font-bold"> / </Label>
<Input <div className="relative">
id="email" <Input
type="email" id="email"
placeholder="name@example.com" type="email"
value={email} placeholder="USER@DOMAIN.COM"
onChange={(e) => setEmail(e.target.value)} value={email}
required 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>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="password"></Label> <Label htmlFor="password" className="font-mono uppercase text-xs font-bold"></Label>
<Input <div className="relative">
id="password" <Input
type="password" id="password"
value={password} type="password"
onChange={(e) => setPassword(e.target.value)} placeholder="••••••••"
required 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> </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> </Button>
</form> </form>
</CardContent>
<CardFooter className="flex flex-col space-y-2"> <div className="mt-6 pt-6 border-t-2 border-dashed border-gray-200 text-center">
<div className="text-sm text-center text-gray-500"> <div className="text-xs font-mono text-gray-500">
<span className="text-blue-600 cursor-pointer hover:underline" onClick={() => navigate('/register')}></span> 访 <span className="text-primary font-bold cursor-pointer hover:underline" onClick={() => navigate('/register')}>访</span>
</div> </div>
</CardFooter> </div>
</Card> </div>
</div>
</div> </div>
); );
} }

View File

@ -1,13 +1,12 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useParams, Link } from "react-router-dom"; import { useParams, Link } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
import { import {
@ -41,7 +40,6 @@ export default function ProjectDetail() {
const [showCreateTaskDialog, setShowCreateTaskDialog] = useState(false); const [showCreateTaskDialog, setShowCreateTaskDialog] = useState(false);
const [showTerminalDialog, setShowTerminalDialog] = useState(false); const [showTerminalDialog, setShowTerminalDialog] = useState(false);
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null); const [currentTaskId, setCurrentTaskId] = useState<string | null>(null);
const [showSettingsDialog, setShowSettingsDialog] = useState(false);
const [editForm, setEditForm] = useState<CreateProjectForm>({ const [editForm, setEditForm] = useState<CreateProjectForm>({
name: "", name: "",
description: "", description: "",
@ -50,6 +48,47 @@ export default function ProjectDetail() {
default_branch: "main", default_branch: "main",
programming_languages: [] programming_languages: []
}); });
const [activeTab, setActiveTab] = useState("overview");
const [latestIssues, setLatestIssues] = useState<any[]>([]);
const [loadingIssues, setLoadingIssues] = useState(false);
useEffect(() => {
if (activeTab === 'issues' && tasks.length > 0) {
loadLatestIssues();
}
}, [activeTab, tasks]);
const loadLatestIssues = async () => {
const completedTasks = tasks.filter(t => t.status === 'completed').sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
if (completedTasks.length > 0) {
setLoadingIssues(true);
try {
const issues = await api.getAuditIssues(completedTasks[0].id);
setLatestIssues(issues);
} catch (error) {
console.error('Failed to load issues:', error);
toast.error("加载问题列表失败");
} finally {
setLoadingIssues(false);
}
}
};
const handleOpenSettings = () => {
if (!project) return;
// 初始化编辑表单
setEditForm({
name: project.name,
description: project.description || "",
repository_url: project.repository_url || "",
repository_type: project.repository_type || "github",
default_branch: project.default_branch || "main",
programming_languages: project.programming_languages ? JSON.parse(project.programming_languages) : []
});
setActiveTab("settings");
};
// 将小写语言名转换为显示格式 // 将小写语言名转换为显示格式
const formatLanguageName = (lang: string): string => { const formatLanguageName = (lang: string): string => {
@ -172,22 +211,6 @@ export default function ProjectDetail() {
} }
}; };
const handleOpenSettings = () => {
if (!project) return;
// 初始化编辑表单
setEditForm({
name: project.name,
description: project.description || "",
repository_url: project.repository_url || "",
repository_type: project.repository_type || "github",
default_branch: project.default_branch || "main",
programming_languages: project.programming_languages ? JSON.parse(project.programming_languages) : []
});
setShowSettingsDialog(true);
};
const handleSaveSettings = async () => { const handleSaveSettings = async () => {
if (!id) return; if (!id) return;
@ -199,7 +222,6 @@ export default function ProjectDetail() {
try { try {
await api.updateProject(id, editForm); await api.updateProject(id, editForm);
toast.success("项目信息已保存"); toast.success("项目信息已保存");
setShowSettingsDialog(false);
loadProjectData(); loadProjectData();
} catch (error) { } catch (error) {
console.error('Failed to update project:', error); console.error('Failed to update project:', error);
@ -258,21 +280,27 @@ export default function ProjectDetail() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen font-mono">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div> <div className="text-center space-y-4">
<div className="relative w-16 h-16 mx-auto">
<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 uppercase font-bold">...</p>
</div>
</div> </div>
); );
} }
if (!project) { if (!project) {
return ( return (
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen font-mono">
<div className="text-center"> <div className="text-center border-2 border-black p-8 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] bg-white">
<AlertTriangle className="w-16 h-16 text-red-500 mx-auto mb-4" /> <AlertTriangle className="w-16 h-16 text-red-600 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-gray-900 mb-2"></h2> <h2 className="text-2xl font-bold text-black mb-2 uppercase"></h2>
<p className="text-gray-600 mb-4">ID是否正确</p> <p className="text-gray-600 mb-4 uppercase">ID是否正确</p>
<Link to="/projects"> <Link to="/projects">
<Button> <Button className="retro-btn">
<ArrowLeft className="w-4 h-4 mr-2" /> <ArrowLeft className="w-4 h-4 mr-2" />
</Button> </Button>
@ -282,126 +310,131 @@ export default function ProjectDetail() {
); );
} }
return ( return (
<div className="space-y-6"> <div className="space-y-6 font-mono">
{/* 页面标题 */} {/* 页面标题 */}
<div className="flex items-center justify-between"> <div className="flex flex-col md:flex-row md:items-center justify-between gap-4 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"> <div className="flex items-start space-x-4">
<Link to="/projects"> <Link to="/projects">
<Button variant="outline" size="sm"> <Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10 w-10 p-0 flex items-center justify-center">
<ArrowLeft className="w-4 h-4 mr-2" /> <ArrowLeft className="w-5 h-5" />
</Button> </Button>
</Link> </Link>
<div> <div>
<h1 className="text-3xl font-bold text-gray-900">{project.name}</h1> <div className="flex items-center gap-2">
<p className="text-gray-600 mt-1"> <h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">{project.name}</h1>
<Badge variant="outline" className={`rounded-none border-2 border-black ${project.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}>
{project.is_active ? '活跃' : '暂停'}
</Badge>
</div>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">
{project.description || '暂无项目描述'} {project.description || '暂无项目描述'}
</p> </p>
</div> </div>
</div> </div>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<Badge variant={project.is_active ? "default" : "secondary"}> <Button onClick={handleRunAudit} disabled={scanning} className="retro-btn bg-primary text-white hover:bg-primary/90">
{project.is_active ? '活跃' : '暂停'}
</Badge>
<Button onClick={handleRunAudit} disabled={scanning}>
<Shield className="w-4 h-4 mr-2" /> <Shield className="w-4 h-4 mr-2" />
{scanning ? '正在启动...' : '启动审计'} {scanning ? '正在启动...' : '启动审计'}
</Button> </Button>
<Button variant="outline" onClick={handleOpenSettings}> <Button variant="outline" onClick={handleOpenSettings} className="retro-btn bg-white text-black hover:bg-gray-100">
<Edit className="w-4 h-4 mr-2" /> <Edit className="w-4 h-4 mr-2" />
</Button> </Button>
</div> </div>
</div> </div>
{/* 项目概览 */} {/* ... (stats cards remain same) ... */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card> {/* ... (stats cards content) ... */}
<CardContent className="p-6"> <div 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">
<div className="flex items-center"> <div className="flex items-center justify-between">
<Activity className="h-8 w-8 text-blue-600" /> <div>
<div className="ml-4"> <p className="text-xs font-bold uppercase text-gray-500"></p>
<p className="text-sm font-medium text-muted-foreground"></p> <p className="text-3xl font-display font-bold">{tasks.length}</p>
<p className="text-2xl font-bold">{tasks.length}</p>
</div>
</div> </div>
</CardContent> <div className="w-12 h-12 border-2 border-black bg-blue-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
</Card> <Activity className="w-6 h-6" />
</div>
</div>
</div>
<Card> <div 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">
<CardContent className="p-6"> <div className="flex items-center justify-between">
<div className="flex items-center"> <div>
<CheckCircle className="h-8 w-8 text-green-600" /> <p className="text-xs font-bold uppercase text-gray-500"></p>
<div className="ml-4"> <p className="text-3xl font-display font-bold">
<p className="text-sm font-medium text-muted-foreground"></p> {tasks.filter(t => t.status === 'completed').length}
<p className="text-2xl font-bold"> </p>
{tasks.filter(t => t.status === 'completed').length}
</p>
</div>
</div> </div>
</CardContent> <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)]">
</Card> <CheckCircle className="w-6 h-6" />
</div>
</div>
</div>
<Card> <div 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">
<CardContent className="p-6"> <div className="flex items-center justify-between">
<div className="flex items-center"> <div>
<AlertTriangle className="h-8 w-8 text-orange-600" /> <p className="text-xs font-bold uppercase text-gray-500"></p>
<div className="ml-4"> <p className="text-3xl font-display font-bold">
<p className="text-sm font-medium text-muted-foreground"></p> {tasks.reduce((sum, task) => sum + task.issues_count, 0)}
<p className="text-2xl font-bold"> </p>
{tasks.reduce((sum, task) => sum + task.issues_count, 0)}
</p>
</div>
</div> </div>
</CardContent> <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)]">
</Card> <AlertTriangle className="w-6 h-6" />
</div>
</div>
</div>
<Card> <div 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">
<CardContent className="p-6"> <div className="flex items-center justify-between">
<div className="flex items-center"> <div>
<Code className="h-8 w-8 text-purple-600" /> <p className="text-xs font-bold uppercase text-gray-500"></p>
<div className="ml-4"> <p className="text-3xl font-display font-bold">
<p className="text-sm font-medium text-muted-foreground"></p> {tasks.length > 0
<p className="text-2xl font-bold"> ? (tasks.reduce((sum, task) => sum + task.quality_score, 0) / tasks.length).toFixed(1)
{tasks.length > 0 : '0.0'
? (tasks.reduce((sum, task) => sum + task.quality_score, 0) / tasks.length).toFixed(1) }
: '0.0' </p>
}
</p>
</div>
</div> </div>
</CardContent> <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)]">
</Card> <Code className="w-6 h-6" />
</div>
</div>
</div>
</div> </div>
{/* 主要内容 */} {/* 主要内容 */}
<Tabs defaultValue="overview" className="w-full"> <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-4"> <TabsList className="grid w-full grid-cols-4 bg-transparent border-2 border-black p-0 h-auto gap-0">
<TabsTrigger value="overview"></TabsTrigger> <TabsTrigger value="overview" className="rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10"></TabsTrigger>
<TabsTrigger value="tasks"></TabsTrigger> <TabsTrigger value="tasks" className="rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10"></TabsTrigger>
<TabsTrigger value="issues"></TabsTrigger> <TabsTrigger value="issues" className="rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10"></TabsTrigger>
<TabsTrigger value="settings"></TabsTrigger> <TabsTrigger value="settings" className="rounded-none data-[state=active]:bg-primary data-[state=active]:text-white font-mono font-bold uppercase h-10"></TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="overview" className="space-y-6"> <TabsContent value="overview" className="space-y-6 mt-6">
{/* ... (overview content remains same) ... */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 项目信息 */} {/* 项目信息 */}
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<CardHeader> <div className="pb-3 border-b-2 border-black mb-4">
<CardTitle></CardTitle> <h3 className="text-lg font-display font-bold uppercase"></h3>
</CardHeader> </div>
<CardContent className="space-y-4"> <div className="space-y-4 font-mono">
<div className="space-y-3"> <div className="space-y-3">
{project.repository_url && ( {project.repository_url && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium"></span> <span className="text-sm font-bold text-gray-600 uppercase"></span>
<a <a
href={project.repository_url} href={project.repository_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-sm text-blue-600 hover:underline flex items-center" className="text-sm text-primary hover:underline flex items-center font-bold"
> >
<ExternalLink className="w-3 h-3 ml-1" /> <ExternalLink className="w-3 h-3 ml-1" />
@ -410,28 +443,28 @@ export default function ProjectDetail() {
)} )}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium"></span> <span className="text-sm font-bold text-gray-600 uppercase"></span>
<Badge variant="outline"> <Badge variant="outline" className="rounded-none border-black bg-gray-100 text-black">
{project.repository_type === 'github' ? 'GitHub' : {project.repository_type === 'github' ? 'GitHub' :
project.repository_type === 'gitlab' ? 'GitLab' : '其他'} project.repository_type === 'gitlab' ? 'GitLab' : '其他'}
</Badge> </Badge>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium"></span> <span className="text-sm font-bold text-gray-600 uppercase"></span>
<span className="text-sm text-muted-foreground">{project.default_branch}</span> <span className="text-sm font-bold text-black bg-gray-100 px-2 border border-black">{project.default_branch}</span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium"></span> <span className="text-sm font-bold text-gray-600 uppercase"></span>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-black">
{formatDate(project.created_at)} {formatDate(project.created_at)}
</span> </span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium"></span> <span className="text-sm font-bold text-gray-600 uppercase"></span>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-black">
{project.owner?.full_name || project.owner?.phone || '未知'} {project.owner?.full_name || project.owner?.phone || '未知'}
</span> </span>
</div> </div>
@ -439,64 +472,67 @@ export default function ProjectDetail() {
{/* 编程语言 */} {/* 编程语言 */}
{project.programming_languages && ( {project.programming_languages && (
<div> <div className="pt-4 border-t-2 border-dashed border-gray-300">
<h4 className="text-sm font-medium mb-2"></h4> <h4 className="text-sm font-bold mb-2 uppercase text-gray-600"></h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{JSON.parse(project.programming_languages).map((lang: string) => ( {JSON.parse(project.programming_languages).map((lang: string) => (
<Badge key={lang} variant="outline"> <Badge key={lang} variant="outline" className="rounded-none border-black bg-yellow-100 text-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
{lang} {lang}
</Badge> </Badge>
))} ))}
</div> </div>
</div> </div>
)} )}
</CardContent> </div>
</Card> </div>
{/* 最近活动 */} {/* 最近活动 */}
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<CardHeader> <div className="pb-3 border-b-2 border-black mb-4">
<CardTitle></CardTitle> <h3 className="text-lg font-display font-bold uppercase"></h3>
</CardHeader> </div>
<CardContent> <div>
{tasks.length > 0 ? ( {tasks.length > 0 ? (
<div className="space-y-3"> <div className="space-y-3">
{tasks.slice(0, 5).map((task) => ( {tasks.slice(0, 5).map((task) => (
<div key={task.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"> <div key={task.id} className="flex items-center justify-between p-3 border-2 border-black bg-gray-50 hover:bg-white hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
{getStatusIcon(task.status)} <div className="border-2 border-black p-1 bg-white">
{getStatusIcon(task.status)}
</div>
<div> <div>
<p className="text-sm font-medium"> <p className="text-sm font-bold font-mono uppercase">
{task.task_type === 'repository' ? '仓库审计' : '即时分析'} {task.task_type === 'repository' ? '仓库审计' : '即时分析'}
</p> </p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-gray-500 font-mono">
{formatDate(task.created_at)} {formatDate(task.created_at)}
</p> </p>
</div> </div>
</div> </div>
<Badge className={getStatusColor(task.status)}> <Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
{task.status === 'completed' ? '已完成' : {task.status === 'completed' ? '已完成' :
task.status === 'running' ? '运行中' : task.status === 'running' ? '运行中' :
task.status === 'failed' ? '失败' : '等待中'} task.status === 'failed' ? '失败' : '等待中'}
</Badge> </Badge>
</div> </div>
))} ))}
</div> </div>
) : ( ) : (
<div className="text-center py-8"> <div className="text-center py-8 border-2 border-dashed border-black">
<Activity className="w-12 h-12 text-muted-foreground mx-auto mb-4" /> <Activity className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<p className="text-muted-foreground"></p> <p className="text-gray-500 font-mono uppercase"></p>
</div> </div>
)} )}
</CardContent> </div>
</Card> </div>
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="tasks" className="space-y-6"> <TabsContent value="tasks" className="space-y-6 mt-6">
{/* ... (tasks content remains same) ... */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-lg font-medium"></h3> <h3 className="text-lg font-display font-bold uppercase"></h3>
<Button onClick={handleCreateTask}> <Button onClick={handleCreateTask} className="retro-btn bg-primary text-white hover:bg-primary/90">
<Play className="w-4 h-4 mr-2" /> <Play className="w-4 h-4 mr-2" />
</Button> </Button>
@ -505,96 +541,260 @@ export default function ProjectDetail() {
{tasks.length > 0 ? ( {tasks.length > 0 ? (
<div className="space-y-4"> <div className="space-y-4">
{tasks.map((task) => ( {tasks.map((task) => (
<Card key={task.id}> <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">
<CardContent className="p-6"> <div className="flex items-center justify-between mb-4 pb-4 border-b-2 border-dashed border-gray-300">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center space-x-3">
<div className="flex items-center space-x-3"> <div className="border-2 border-black p-1 bg-white">
{getStatusIcon(task.status)} {getStatusIcon(task.status)}
<div>
<h4 className="font-medium">
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
</h4>
<p className="text-sm text-muted-foreground">
{formatDate(task.created_at)}
</p>
</div>
</div> </div>
<Badge className={getStatusColor(task.status)}> <div>
{task.status === 'completed' ? '已完成' : <h4 className="font-bold font-mono uppercase">
task.status === 'running' ? '运行中' : {task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
task.status === 'failed' ? '失败' : '等待中'} </h4>
</Badge> <p className="text-sm text-gray-500 font-mono">
</div> {formatDate(task.created_at)}
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div className="text-center">
<p className="text-2xl font-bold">{task.total_files}</p>
<p className="text-sm text-muted-foreground"></p>
</div>
<div className="text-center">
<p className="text-2xl font-bold">{task.total_lines}</p>
<p className="text-sm text-muted-foreground"></p>
</div>
<div className="text-center">
<p className="text-2xl font-bold">{task.issues_count}</p>
<p className="text-sm text-muted-foreground"></p>
</div>
<div className="text-center">
<p className="text-2xl font-bold">{task.quality_score.toFixed(1)}</p>
<p className="text-sm text-muted-foreground"></p>
</div> </div>
</div> </div>
<Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
{task.status === 'completed' ? '已完成' :
task.status === 'running' ? '运行中' :
task.status === 'failed' ? '失败' : '等待中'}
</Badge>
</div>
{task.status === 'completed' && ( <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4 font-mono">
<div className="space-y-2"> <div className="text-center p-2 bg-gray-50 border border-gray-200">
<div className="flex items-center justify-between text-sm"> <p className="text-2xl font-bold">{task.total_files}</p>
<span></span> <p className="text-xs text-gray-500 uppercase"></p>
<span>{task.quality_score.toFixed(1)}/100</span>
</div>
<Progress value={task.quality_score} />
</div>
)}
<div className="flex justify-end space-x-2 mt-4">
<Link to={`/tasks/${task.id}`}>
<Button variant="outline" size="sm">
<FileText className="w-4 h-4 mr-2" />
</Button>
</Link>
</div> </div>
</CardContent> <div className="text-center p-2 bg-gray-50 border border-gray-200">
</Card> <p className="text-2xl font-bold">{task.total_lines}</p>
<p className="text-xs text-gray-500 uppercase"></p>
</div>
<div className="text-center p-2 bg-gray-50 border border-gray-200">
<p className="text-2xl font-bold text-orange-600">{task.issues_count}</p>
<p className="text-xs text-gray-500 uppercase"></p>
</div>
<div className="text-center p-2 bg-gray-50 border border-gray-200">
<p className="text-2xl font-bold text-primary">{task.quality_score.toFixed(1)}</p>
<p className="text-xs text-gray-500 uppercase"></p>
</div>
</div>
{task.status === 'completed' && (
<div className="space-y-2 mb-4">
<div className="flex items-center justify-between text-sm font-mono font-bold">
<span></span>
<span>{task.quality_score.toFixed(1)}/100</span>
</div>
<Progress value={task.quality_score} className="h-3 border-2 border-black rounded-none bg-gray-100 [&>div]:bg-primary" />
</div>
)}
<div className="flex justify-end space-x-2 mt-4 pt-4 border-t-2 border-black">
<Link to={`/tasks/${task.id}`}>
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100">
<FileText className="w-4 h-4 mr-2" />
</Button>
</Link>
</div>
</div>
))} ))}
</div> </div>
) : ( ) : (
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-12 flex flex-col items-center justify-center">
<CardContent className="flex flex-col items-center justify-center py-12"> <Activity className="w-16 h-16 text-gray-400 mb-4" />
<Activity className="w-16 h-16 text-muted-foreground mb-4" /> <h3 className="text-lg font-bold text-gray-600 mb-2 uppercase"></h3>
<h3 className="text-lg font-medium text-muted-foreground mb-2"></h3> <p className="text-sm text-gray-500 mb-6 font-mono"></p>
<p className="text-sm text-muted-foreground mb-4"></p> <Button onClick={handleCreateTask} className="retro-btn bg-primary text-white hover:bg-primary/90">
<Button onClick={handleCreateTask}> <Play className="w-4 h-4 mr-2" />
<Play className="w-4 h-4 mr-2" />
</Button>
</Button> </div>
</CardContent>
</Card>
)} )}
</TabsContent> </TabsContent>
<TabsContent value="issues" className="space-y-6"> <TabsContent value="issues" className="space-y-6 mt-6">
<div className="text-center py-12"> <div className="flex items-center justify-between">
<AlertTriangle className="w-16 h-16 text-muted-foreground mx-auto mb-4" /> <h3 className="text-lg font-display font-bold uppercase"></h3>
<h3 className="text-lg font-medium text-muted-foreground mb-2"></h3> {tasks.length > 0 && (
<p className="text-sm text-muted-foreground"></p> <p className="text-sm text-gray-500 font-mono">
({formatDate(tasks[0].created_at)})
</p>
)}
</div> </div>
{loadingIssues ? (
<div className="text-center py-12">
<div className="animate-spin w-8 h-8 border-4 border-primary border-t-transparent rounded-full mx-auto mb-4"></div>
<p className="text-gray-500 font-mono">...</p>
</div>
) : latestIssues.length > 0 ? (
<div className="space-y-4">
{latestIssues.map((issue, index) => (
<div key={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">
<div className="flex items-start justify-between">
<div className="flex items-start space-x-3">
<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'
}`}>
<AlertTriangle className="w-4 h-4" />
</div>
<div>
<h4 className="font-bold text-base text-black mb-1 font-mono uppercase">{issue.title}</h4>
<div className="flex items-center space-x-2 text-xs text-gray-600 font-mono">
<span className="bg-gray-100 px-1 border border-gray-300">{issue.file_path}:{issue.line_number}</span>
<span>{issue.category}</span>
</div>
</div>
</div>
<Badge className={`rounded-none border-2 border-black ${issue.severity === 'critical' ? 'bg-red-100 text-red-800' :
issue.severity === 'high' ? 'bg-orange-100 text-orange-800' :
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-800' :
'bg-blue-100 text-blue-800'
} font-bold uppercase`}>
{issue.severity === 'critical' ? '严重' :
issue.severity === 'high' ? '高' :
issue.severity === 'medium' ? '中等' : '低'}
</Badge>
</div>
<p className="mt-3 text-sm text-gray-700 font-mono border-t-2 border-dashed border-gray-200 pt-2">
{issue.description}
</p>
</div>
))}
</div>
) : (
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-12 flex flex-col items-center justify-center">
<CheckCircle className="w-16 h-16 text-green-500 mb-4" />
<h3 className="text-lg font-bold text-gray-600 mb-2 uppercase"></h3>
<p className="text-sm text-gray-500 font-mono"></p>
</div>
)}
</TabsContent> </TabsContent>
<TabsContent value="settings" className="space-y-6"> <TabsContent value="settings" className="space-y-6 mt-6">
<div className="text-center py-12"> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6">
<Edit className="w-16 h-16 text-muted-foreground mx-auto mb-4" /> <div className="pb-4 border-b-2 border-black mb-6">
<h3 className="text-lg font-medium text-muted-foreground mb-2"></h3> <h3 className="text-lg font-display font-bold uppercase flex items-center">
<p className="text-sm text-muted-foreground"></p> <Edit className="w-5 h-5 mr-2" />
</h3>
</div>
<div className="space-y-6">
{/* 基本信息 */}
<div className="space-y-4">
<div>
<Label htmlFor="edit-name" className="font-bold font-mono uppercase"> *</Label>
<Input
id="edit-name"
value={editForm.name}
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
placeholder="输入项目名称"
className="retro-input mt-1"
/>
</div>
<div>
<Label htmlFor="edit-description" className="font-bold font-mono uppercase"></Label>
<Textarea
id="edit-description"
value={editForm.description}
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
placeholder="输入项目描述"
rows={3}
className="retro-input mt-1 min-h-[80px]"
/>
</div>
</div>
{/* 仓库信息 */}
<div className="space-y-4 border-t-2 border-dashed border-gray-300 pt-4">
<h3 className="text-sm font-bold font-mono uppercase text-gray-900 bg-gray-100 inline-block px-2 border border-black"></h3>
<div>
<Label htmlFor="edit-repo-url" className="font-bold font-mono uppercase"></Label>
<Input
id="edit-repo-url"
value={editForm.repository_url}
onChange={(e) => setEditForm({ ...editForm, repository_url: e.target.value })}
placeholder="https://github.com/username/repo"
className="retro-input mt-1"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label htmlFor="edit-repo-type" className="font-bold font-mono uppercase"></Label>
<Select
value={editForm.repository_type}
onValueChange={(value: any) => setEditForm({ ...editForm, repository_type: value })}
>
<SelectTrigger id="edit-repo-type" className="retro-input mt-1 rounded-none border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] focus:ring-0">
<SelectValue />
</SelectTrigger>
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<SelectItem value="github">GitHub</SelectItem>
<SelectItem value="gitlab">GitLab</SelectItem>
<SelectItem value="other"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="edit-branch" className="font-bold font-mono uppercase"></Label>
<Input
id="edit-branch"
value={editForm.default_branch}
onChange={(e) => setEditForm({ ...editForm, default_branch: e.target.value })}
placeholder="main"
className="retro-input mt-1"
/>
</div>
</div>
</div>
{/* 编程语言 */}
<div className="space-y-4 border-t-2 border-dashed border-gray-300 pt-4">
<h3 className="text-sm font-bold font-mono uppercase text-gray-900 bg-gray-100 inline-block px-2 border border-black"></h3>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{supportedLanguages.map((lang) => (
<div
key={lang}
className={`flex items-center space-x-2 p-3 border-2 cursor-pointer transition-all hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] ${editForm.programming_languages?.includes(lang)
? 'border-black bg-yellow-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]'
: 'border-gray-300 hover:border-black'
}`}
onClick={() => handleToggleLanguage(lang)}
>
<div
className={`w-4 h-4 border-2 flex items-center justify-center ${editForm.programming_languages?.includes(lang)
? 'bg-black border-black'
: 'border-gray-400 bg-white'
}`}
>
{editForm.programming_languages?.includes(lang) && (
<CheckCircle className="w-3 h-3 text-white" />
)}
</div>
<span className="text-sm font-bold font-mono">{lang}</span>
</div>
))}
</div>
</div>
<div className="flex justify-end space-x-3 pt-6 border-t-2 border-black">
<Button onClick={handleSaveSettings} className="retro-btn bg-primary text-white hover:bg-primary/90 w-full md:w-auto">
<Edit className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
</div> </div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
@ -614,125 +814,6 @@ export default function ProjectDetail() {
taskId={currentTaskId} taskId={currentTaskId}
taskType="repository" taskType="repository"
/> />
{/* 项目编辑对话框 */}
<Dialog open={showSettingsDialog} onOpenChange={setShowSettingsDialog}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-6 py-4">
{/* 基本信息 */}
<div className="space-y-4">
<div>
<Label htmlFor="edit-name"> *</Label>
<Input
id="edit-name"
value={editForm.name}
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
placeholder="输入项目名称"
/>
</div>
<div>
<Label htmlFor="edit-description"></Label>
<Textarea
id="edit-description"
value={editForm.description}
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
placeholder="输入项目描述"
rows={3}
/>
</div>
</div>
{/* 仓库信息 */}
<div className="space-y-4">
<h3 className="text-sm font-semibold text-gray-900"></h3>
<div>
<Label htmlFor="edit-repo-url"></Label>
<Input
id="edit-repo-url"
value={editForm.repository_url}
onChange={(e) => setEditForm({ ...editForm, repository_url: e.target.value })}
placeholder="https://github.com/username/repo"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="edit-repo-type"></Label>
<Select
value={editForm.repository_type}
onValueChange={(value: any) => setEditForm({ ...editForm, repository_type: value })}
>
<SelectTrigger id="edit-repo-type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="github">GitHub</SelectItem>
<SelectItem value="gitlab">GitLab</SelectItem>
<SelectItem value="other"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="edit-branch"></Label>
<Input
id="edit-branch"
value={editForm.default_branch}
onChange={(e) => setEditForm({ ...editForm, default_branch: e.target.value })}
placeholder="main"
/>
</div>
</div>
</div>
{/* 编程语言 */}
<div className="space-y-4">
<h3 className="text-sm font-semibold text-gray-900"></h3>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{supportedLanguages.map((lang) => (
<div
key={lang}
className={`flex items-center space-x-2 p-3 rounded-lg border cursor-pointer transition-all ${
editForm.programming_languages?.includes(lang)
? 'border-primary bg-red-50'
: 'border-gray-200 hover:border-gray-300'
}`}
onClick={() => handleToggleLanguage(lang)}
>
<div
className={`w-4 h-4 rounded border flex items-center justify-center ${
editForm.programming_languages?.includes(lang)
? 'bg-primary border-primary'
: 'border-gray-300'
}`}
>
{editForm.programming_languages?.includes(lang) && (
<CheckCircle className="w-3 h-3 text-white" />
)}
</div>
<span className="text-sm font-medium">{lang}</span>
</div>
))}
</div>
</div>
</div>
<div className="flex justify-end space-x-3 pt-4 border-t">
<Button variant="outline" onClick={() => setShowSettingsDialog(false)}>
</Button>
<Button onClick={handleSaveSettings}>
</Button>
</div>
</DialogContent>
</Dialog>
</div> </div>
); );
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -113,10 +113,10 @@ export default function RecycleBin() {
if (loading) { if (loading) {
return ( 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="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div> <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-gray-500">...</p> <p className="text-black font-mono font-bold uppercase">...</p>
</div> </div>
</div> </div>
); );
@ -125,72 +125,68 @@ export default function RecycleBin() {
return ( return (
<div className="space-y-6 animate-fade-in"> <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> <div>
<h1 className="page-title flex items-center gap-2"> <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-gray-400" /> <Trash2 className="w-8 h-8 text-black" />
</h1> </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>
</div> </div>
{/* 搜索 */} {/* 搜索 */}
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
<CardContent className="p-4"> <div className="flex items-center space-x-4">
<div className="flex items-center space-x-4"> <div className="flex-1 relative">
<div className="flex-1 relative"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" /> <Input
<Input placeholder="搜索已删除的项目..."
placeholder="搜索已删除的项目..." value={searchTerm}
value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)}
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"
className="pl-10" />
/>
</div>
</div> </div>
</CardContent> </div>
</Card> </div>
{/* 项目列表 */} {/* 项目列表 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredProjects.length > 0 ? ( {filteredProjects.length > 0 ? (
filteredProjects.map((project) => ( filteredProjects.map((project) => (
<Card key={project.id} className="card-modern group opacity-75 hover:opacity-100 transition-opacity"> <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">
<CardHeader className="pb-4"> <div className="p-4 border-b-2 border-black bg-gray-50 flex items-start justify-between">
<div className="flex items-start justify-between"> <div className="flex items-center space-x-3">
<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)]">
<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)}
{getRepositoryIcon(project.repository_type)} </div>
</div> <div>
<div> <h3 className="text-lg font-bold font-display uppercase truncate max-w-[150px]">
<CardTitle className="text-lg"> {project.name}
{project.name} </h3>
</CardTitle> {project.description && (
{project.description && ( <p className="text-xs text-gray-500 mt-1 line-clamp-1 font-mono">
<p className="text-sm text-gray-500 mt-1 line-clamp-2"> {project.description}
{project.description} </p>
</p> )}
)}
</div>
</div> </div>
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700">
</Badge>
</div> </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"> <div className="space-y-3">
{project.repository_url && ( {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" /> <GitBranch className="w-4 h-4 mr-2 flex-shrink-0" />
<a <a
href={project.repository_url} href={project.repository_url}
target="_blank" target="_blank"
rel="noopener noreferrer" 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> <span className="truncate">{project.repository_url.replace('https://', '')}</span>
<ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" /> <ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" />
@ -198,7 +194,7 @@ export default function RecycleBin() {
</div> </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"> <div className="flex items-center">
<Calendar className="w-4 h-4 mr-2" /> <Calendar className="w-4 h-4 mr-2" />
{formatDate(project.updated_at)} {formatDate(project.updated_at)}
@ -214,12 +210,12 @@ export default function RecycleBin() {
{project.programming_languages && ( {project.programming_languages && (
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => ( {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} {lang}
</Badge> </Badge>
))} ))}
{JSON.parse(project.programming_languages).length > 4 && ( {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} +{JSON.parse(project.programming_languages).length - 4}
</Badge> </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 <Button
size="sm" size="sm"
variant="outline" 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)} onClick={() => handleRestoreClick(project)}
> >
<RotateCcw className="w-4 h-4 mr-2" /> <RotateCcw className="w-4 h-4 mr-2" />
@ -240,52 +236,55 @@ export default function RecycleBin() {
<Button <Button
size="sm" size="sm"
variant="outline" 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)} onClick={() => handlePermanentDeleteClick(project)}
> >
<Trash2 className="w-4 h-4 mr-2" /> <Trash2 className="w-4 h-4 mr-2" />
</Button> </Button>
</div> </div>
</CardContent> </div>
</Card> </div>
)) ))
) : ( ) : (
<div className="col-span-full"> <div className="col-span-full">
<Card className="card-modern"> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<CardContent className="empty-state py-16"> <div className="py-16 flex flex-col items-center justify-center text-center">
<div className="empty-icon"> <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-8 h-8 text-primary" /> <Inbox className="w-10 h-10 text-gray-400" />
</div> </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 ? '未找到匹配的项目' : '回收站为空'} {searchTerm ? '未找到匹配的项目' : '回收站为空'}
</h3> </h3>
<p className="text-gray-500 mb-6 max-w-md"> <p className="text-gray-500 font-mono max-w-md">
{searchTerm ? '尝试调整搜索条件' : '回收站中没有已删除的项目'} {searchTerm ? '尝试调整搜索条件' : '回收站中没有已删除的项目'}
</p> </p>
</CardContent> </div>
</Card> </div>
</div> </div>
)} )}
</div> </div>
{/* 恢复项目确认对话框 */} {/* 恢复项目确认对话框 */}
<AlertDialog open={showRestoreDialog} onOpenChange={setShowRestoreDialog}> <AlertDialog open={showRestoreDialog} onOpenChange={setShowRestoreDialog}>
<AlertDialogContent> <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> <AlertDialogHeader className="p-6 border-b-2 border-black bg-gray-50">
<AlertDialogTitle></AlertDialogTitle> <AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2">
<AlertDialogDescription> <RotateCcw className="w-6 h-6 text-green-600" />
<span className="font-semibold text-gray-900">"{selectedProject?.name}"</span>
</AlertDialogTitle>
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
<span className="font-bold text-black">"{selectedProject?.name}"</span>
<br /> <br />
<br /> <br />
使 使
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter className="p-6 bg-white flex gap-3">
<AlertDialogCancel></AlertDialogCancel> <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 <AlertDialogAction
onClick={handleConfirmRestore} 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> </AlertDialogAction>
@ -295,19 +294,22 @@ export default function RecycleBin() {
{/* 永久删除确认对话框 */} {/* 永久删除确认对话框 */}
<AlertDialog open={showPermanentDeleteDialog} onOpenChange={setShowPermanentDeleteDialog}> <AlertDialog open={showPermanentDeleteDialog} onOpenChange={setShowPermanentDeleteDialog}>
<AlertDialogContent> <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> <AlertDialogHeader className="p-6 border-b-2 border-black bg-red-50">
<AlertDialogTitle className="flex items-center gap-2 text-red-600"> <AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2 text-red-600">
<AlertTriangle className="w-5 h-5" /> <AlertTriangle className="w-6 h-6" />
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription className="mt-4 font-mono text-gray-600">
<span className="font-semibold text-red-600"></span> <span className="font-semibold text-gray-900">"{selectedProject?.name}"</span> <span className="font-bold text-red-600 uppercase"></span> <span className="font-bold text-black">"{selectedProject?.name}"</span>
<br /> <br />
<br /> <br />
<div className="bg-red-50 border border-red-200 rounded-lg p-4 my-3"> <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-semibold mb-2"> </p> <p className="text-red-800 font-bold mb-2 uppercase flex items-center">
<ul className="list-disc list-inside text-red-700 space-y-1 text-sm"> <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> <li></li>
<li></li> <li></li>
@ -315,11 +317,11 @@ export default function RecycleBin() {
</div> </div>
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter className="p-6 bg-white flex gap-3">
<AlertDialogCancel></AlertDialogCancel> <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 <AlertDialogAction
onClick={handleConfirmPermanentDelete} 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> </AlertDialogAction>

View File

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

View File

@ -205,6 +205,7 @@ export const api = {
completed_tasks: number; completed_tasks: number;
total_issues: number; total_issues: number;
resolved_issues: number; resolved_issues: number;
avg_quality_score: number;
}> { }> {
try { try {
const res = await apiClient.get('/projects/stats'); const res = await apiClient.get('/projects/stats');
@ -216,7 +217,8 @@ export const api = {
total_tasks: 0, total_tasks: 0,
completed_tasks: 0, completed_tasks: 0,
total_issues: 0, total_issues: 0,
resolved_issues: 0 resolved_issues: 0,
avg_quality_score: 0
}; };
} }
}, },

View File

@ -18,11 +18,13 @@ export default {
}, },
}, },
extend: { extend: {
fontFamily: {
mono: ['"Space Mono"', '"Courier New"', 'monospace'],
sans: ['"Inter"', 'system-ui', 'sans-serif'],
display: ['"Orbitron"', 'sans-serif'],
},
colors: { colors: {
border: 'hsl(var(--border))', border: 'hsl(var(--border))',
borderColor: {
border: 'hsl(var(--border))',
},
input: 'hsl(var(--input))', input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))', ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))', background: 'hsl(var(--background))',
@ -55,127 +57,62 @@ export default {
DEFAULT: 'hsl(var(--card))', DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))', foreground: 'hsl(var(--card-foreground))',
}, },
// 专业深红色调色板 // Cassette Futurism Palette
crimson: { retro: {
50: '#fef2f2', orange: '#FF3D00',
100: '#fee2e2', teal: '#00E5FF',
200: '#fecaca', pink: '#FF00FF',
300: '#fca5a5', yellow: '#FFEA00',
400: '#f87171', dark: '#1A1A1A',
500: '#ef4444', light: '#F5F7FA',
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))',
},
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)', md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)', sm: 'calc(var(--radius) - 4px)',
}, none: '0',
backgroundImage: {
'gradient-primary': 'var(--gradient-primary)',
'gradient-card': 'var(--gradient-card)',
'gradient-background': 'var(--gradient-background)',
}, },
boxShadow: { boxShadow: {
card: 'var(--shadow-card)', 'retro': '4px 4px 0px 0px rgba(0,0,0,1)',
hover: 'var(--shadow-hover)', '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: { keyframes: {
'accordion-down': { 'accordion-down': {
from: { from: { height: '0' },
height: '0', to: { height: 'var(--radix-accordion-content-height)' },
},
to: {
height: 'var(--radix-accordion-content-height)',
},
}, },
'accordion-up': { 'accordion-up': {
from: { from: { height: 'var(--radix-accordion-content-height)' },
height: 'var(--radix-accordion-content-height)', to: { height: '0' },
},
to: {
height: '0',
},
}, },
'fade-in': { 'glitch': {
from: { '0%, 100%': { transform: 'translate(0)' },
opacity: '0', '20%': { transform: 'translate(-2px, 2px)' },
transform: 'translateY(10px)', '40%': { transform: 'translate(-2px, -2px)' },
}, '60%': { transform: 'translate(2px, 2px)' },
to: { '80%': { transform: 'translate(2px, -2px)' },
opacity: '1',
transform: 'translateY(0)',
},
}, },
'slide-in': { 'scanline': {
from: { '0%': { transform: 'translateY(-100%)' },
opacity: '0', '100%': { transform: 'translateY(100%)' },
transform: 'translateX(-20px)',
},
to: {
opacity: '1',
transform: 'translateX(0)',
},
}, },
'pulse-slow': {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.5' },
}
}, },
animation: { animation: {
'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out',
'fade-in': 'fade-in 0.5s ease-out', 'glitch': 'glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite',
'slide-in': 'slide-in 0.5s ease-out', 'scanline': 'scanline 8s linear infinite',
'pulse-slow': 'pulse-slow 3s ease-in-out infinite',
}, },
}, },
}, },
plugins: [ plugins: [
require('tailwindcss-animate'), 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']
);
},
], ],
}; };