feat: Implement retro-futuristic UI design across frontend pages and components.
This commit is contained in:
parent
7315770a17
commit
53c8c27ee7
|
|
@ -4,48 +4,65 @@
|
|||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--background: 210 20% 98%;
|
||||
--foreground: 220 10% 10%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--card-foreground: 220 10% 10%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
--popover-foreground: 220 10% 10%;
|
||||
|
||||
--primary: 220 100% 50%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
--secondary: 15 100% 50%;
|
||||
--secondary-foreground: 0 0% 100%;
|
||||
|
||||
--muted: 210 20% 90%;
|
||||
--muted-foreground: 220 10% 40%;
|
||||
|
||||
--accent: 180 100% 40%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
|
||||
--border: 0 0% 0%;
|
||||
--input: 0 0% 0%;
|
||||
--ring: 220 100% 50%;
|
||||
|
||||
--radius: 0rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--background: 220 10% 10%;
|
||||
--foreground: 210 20% 98%;
|
||||
|
||||
--card: 220 10% 15%;
|
||||
--card-foreground: 210 20% 98%;
|
||||
|
||||
--popover: 220 10% 15%;
|
||||
--popover-foreground: 210 20% 98%;
|
||||
|
||||
--primary: 220 100% 50%;
|
||||
--primary-foreground: 0 0% 100%;
|
||||
|
||||
--secondary: 15 100% 50%;
|
||||
--secondary-foreground: 0 0% 100%;
|
||||
|
||||
--muted: 220 10% 20%;
|
||||
--muted-foreground: 210 20% 60%;
|
||||
|
||||
--accent: 180 100% 40%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
|
||||
--border: 0 0% 100%;
|
||||
--input: 0 0% 100%;
|
||||
--ring: 220 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,8 +70,48 @@
|
|||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@apply bg-background text-foreground font-mono antialiased;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-display font-bold tracking-tight;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.retro-border {
|
||||
@apply border-2 border-black;
|
||||
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.retro-card {
|
||||
@apply bg-white border-2 border-black p-4;
|
||||
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.retro-btn {
|
||||
@apply bg-primary text-white font-bold py-2 px-4 border-2 border-black transition-all;
|
||||
box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.retro-btn:hover {
|
||||
box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 1);
|
||||
transform: translate(2px, 2px);
|
||||
}
|
||||
|
||||
.retro-btn:active {
|
||||
box-shadow: none;
|
||||
transform: translate(4px, 4px);
|
||||
}
|
||||
|
||||
.retro-input {
|
||||
@apply bg-white border-2 border-black p-2 focus:outline-none focus:ring-2 focus:ring-primary shadow-none;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import { Label } from "@/components/ui/label";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
GitBranch,
|
||||
|
|
@ -208,7 +207,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
console.log('✅ 任务创建成功:', taskId);
|
||||
|
||||
// 记录用户操作
|
||||
import('@/shared/utils/logger').then(({ logger, LogCategory }) => {
|
||||
import('@/shared/utils/logger').then(({ logger }) => {
|
||||
logger.logUserAction('创建审计任务', {
|
||||
taskId,
|
||||
projectId: project.id,
|
||||
|
|
@ -299,76 +298,74 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center space-x-2">
|
||||
<Shield className="w-5 h-5 text-primary" />
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto bg-white border-2 border-black p-0 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none">
|
||||
<DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
|
||||
<DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
|
||||
<Shield className="w-6 h-6 text-black" />
|
||||
<span>新建审计任务</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 项目选择 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-base font-medium">选择项目</Label>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Label className="text-base font-bold font-mono uppercase">选择项目</Label>
|
||||
<Badge variant="outline" className="text-xs rounded-none border-black font-mono">
|
||||
{filteredProjects.length} 个可用项目
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* 项目搜索 */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||
<Input
|
||||
placeholder="搜索项目名称..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
className="pl-10 retro-input h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 项目列表 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-60 overflow-y-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-60 overflow-y-auto p-1">
|
||||
{loading ? (
|
||||
<div className="col-span-2 flex items-center justify-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
<div className="animate-spin rounded-none h-8 w-8 border-4 border-primary border-t-transparent"></div>
|
||||
</div>
|
||||
) : filteredProjects.length > 0 ? (
|
||||
filteredProjects.map((project) => (
|
||||
<Card
|
||||
<div
|
||||
key={project.id}
|
||||
className={`cursor-pointer transition-all hover:shadow-md ${taskForm.project_id === project.id
|
||||
? 'ring-2 ring-primary bg-primary/5'
|
||||
: 'hover:bg-gray-50'
|
||||
className={`cursor-pointer transition-all border-2 p-4 relative ${taskForm.project_id === project.id
|
||||
? 'border-primary bg-blue-50 shadow-[4px_4px_0px_0px_rgba(37,99,235,1)] translate-x-[-2px] translate-y-[-2px]'
|
||||
: 'border-black bg-white hover:bg-gray-50 hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px]'
|
||||
}`}
|
||||
onClick={() => setTaskForm({ ...taskForm, project_id: project.id })}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium text-sm">{project.name}</h4>
|
||||
{project.description && (
|
||||
<p className="text-xs text-gray-500 mt-1 line-clamp-2">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-400">
|
||||
<span>{project.repository_type?.toUpperCase() || 'OTHER'}</span>
|
||||
<span>{project.default_branch}</span>
|
||||
</div>
|
||||
</div>
|
||||
{taskForm.project_id === project.id && (
|
||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center">
|
||||
<div className="w-2 h-2 rounded-full bg-white"></div>
|
||||
</div>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-bold text-sm font-display uppercase">{project.name}</h4>
|
||||
{project.description && (
|
||||
<p className="text-xs text-gray-600 mt-1 line-clamp-2 font-mono">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-500 font-mono font-bold">
|
||||
<span className="uppercase">{project.repository_type?.toUpperCase() || 'OTHER'}</span>
|
||||
<span>{project.default_branch}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{taskForm.project_id === project.id && (
|
||||
<div className="w-5 h-5 bg-primary border-2 border-black flex items-center justify-center">
|
||||
<div className="w-2 h-2 bg-white"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-2 text-center py-8 text-gray-500">
|
||||
<div className="col-span-2 text-center py-8 text-gray-500 font-mono">
|
||||
<FileText className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">
|
||||
{searchTerm ? '未找到匹配的项目' : '暂无可用项目'}
|
||||
|
|
@ -381,133 +378,141 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
{/* 任务配置 */}
|
||||
{selectedProject && (
|
||||
<Tabs defaultValue="basic" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="basic" className="flex items-center space-x-2">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-gray-100 border-2 border-black p-0 h-12 rounded-none">
|
||||
<TabsTrigger
|
||||
value="basic"
|
||||
className="flex items-center space-x-2 rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
|
||||
>
|
||||
<GitBranch className="w-4 h-4" />
|
||||
<span>基础配置</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="exclude" className="flex items-center space-x-2">
|
||||
<TabsTrigger
|
||||
value="exclude"
|
||||
className="flex items-center space-x-2 rounded-none border-r-2 border-black data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
|
||||
>
|
||||
<FileText className="w-4 h-4" />
|
||||
<span>排除规则</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="advanced" className="flex items-center space-x-2">
|
||||
<TabsTrigger
|
||||
value="advanced"
|
||||
className="flex items-center space-x-2 rounded-none data-[state=active]:bg-primary data-[state=active]:text-white h-full font-bold uppercase transition-all"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
<span>高级选项</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="basic" className="space-y-4 mt-6">
|
||||
<TabsContent value="basic" className="space-y-4 mt-6 font-mono">
|
||||
{/* ZIP项目文件上传 */}
|
||||
{(!selectedProject.repository_url || selectedProject.repository_url.trim() === '') && (
|
||||
<Card className="bg-amber-50 border-amber-200">
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
{loadingZipFile ? (
|
||||
<div className="flex items-center space-x-3 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div>
|
||||
<p className="text-sm text-blue-800">正在加载保存的ZIP文件...</p>
|
||||
<div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="space-y-3">
|
||||
{loadingZipFile ? (
|
||||
<div className="flex items-center space-x-3 p-4 bg-blue-50 border-2 border-black">
|
||||
<div className="animate-spin rounded-none h-5 w-5 border-4 border-blue-600 border-t-transparent"></div>
|
||||
<p className="text-sm text-blue-800 font-bold">正在加载保存的ZIP文件...</p>
|
||||
</div>
|
||||
) : zipFile ? (
|
||||
<div className="flex items-start space-x-3 p-4 bg-green-50 border-2 border-black">
|
||||
<Info className="w-5 h-5 text-green-600 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-bold text-green-900 text-sm uppercase">已准备就绪</p>
|
||||
<p className="text-xs text-green-700 mt-1 font-bold">
|
||||
使用保存的ZIP文件: {zipFile.name} (
|
||||
{zipFile.size >= 1024 * 1024
|
||||
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB`
|
||||
: zipFile.size >= 1024
|
||||
? `${(zipFile.size / 1024).toFixed(2)} KB`
|
||||
: `${zipFile.size} B`
|
||||
})
|
||||
</p>
|
||||
</div>
|
||||
) : zipFile ? (
|
||||
<div className="flex items-start space-x-3 p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<Info className="w-5 h-5 text-green-600 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-green-900 text-sm">已准备就绪</p>
|
||||
<p className="text-xs text-green-700 mt-1">
|
||||
使用保存的ZIP文件: {zipFile.name} (
|
||||
{zipFile.size >= 1024 * 1024
|
||||
? `${(zipFile.size / 1024 / 1024).toFixed(2)} MB`
|
||||
: zipFile.size >= 1024
|
||||
? `${(zipFile.size / 1024).toFixed(2)} KB`
|
||||
: `${zipFile.size} B`
|
||||
})
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setZipFile(null);
|
||||
setHasLoadedZip(false);
|
||||
}}
|
||||
className="retro-btn bg-white text-black h-8 text-xs"
|
||||
>
|
||||
更换文件
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start space-x-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-bold text-amber-900 text-sm uppercase">需要上传ZIP文件</p>
|
||||
<p className="text-xs text-amber-700 mt-1 font-bold">
|
||||
未找到保存的ZIP文件,请上传文件进行扫描
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setZipFile(null);
|
||||
setHasLoadedZip(false);
|
||||
}}
|
||||
>
|
||||
更换文件
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start space-x-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-amber-900 text-sm">需要上传ZIP文件</p>
|
||||
<p className="text-xs text-amber-700 mt-1">
|
||||
未找到保存的ZIP文件,请上传文件进行扫描
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="zipFile">上传ZIP文件</Label>
|
||||
<Input
|
||||
id="zipFile"
|
||||
type="file"
|
||||
accept=".zip"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
console.log('📁 选择的文件:', {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
sizeMB: (file.size / 1024 / 1024).toFixed(2)
|
||||
});
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="zipFile" className="font-bold uppercase">上传ZIP文件</Label>
|
||||
<Input
|
||||
id="zipFile"
|
||||
type="file"
|
||||
accept=".zip"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
console.log('📁 选择的文件:', {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
sizeMB: (file.size / 1024 / 1024).toFixed(2)
|
||||
});
|
||||
|
||||
const validation = validateZipFile(file);
|
||||
if (!validation.valid) {
|
||||
toast.error(validation.error || "文件无效");
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
setZipFile(file);
|
||||
setHasLoadedZip(true);
|
||||
|
||||
const sizeMB = (file.size / 1024 / 1024).toFixed(2);
|
||||
const sizeKB = (file.size / 1024).toFixed(2);
|
||||
const sizeText = file.size >= 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
|
||||
|
||||
toast.success(`已选择文件: ${file.name} (${sizeText})`);
|
||||
const validation = validateZipFile(file);
|
||||
if (!validation.valid) {
|
||||
toast.error(validation.error || "文件无效");
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
setZipFile(file);
|
||||
setHasLoadedZip(true);
|
||||
|
||||
const sizeMB = (file.size / 1024 / 1024).toFixed(2);
|
||||
const sizeKB = (file.size / 1024).toFixed(2);
|
||||
const sizeText = file.size >= 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
|
||||
|
||||
toast.success(`已选择文件: ${file.name} (${sizeText})`);
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer retro-input pt-1.5"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task_type">任务类型</Label>
|
||||
<Label htmlFor="task_type" className="font-bold uppercase">任务类型</Label>
|
||||
<Select
|
||||
value={taskForm.task_type}
|
||||
onValueChange={(value: any) => setTaskForm({ ...taskForm, task_type: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="retro-input h-10 rounded-none border-2 border-black shadow-none focus:ring-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<SelectItem value="repository">
|
||||
<div className="flex items-center space-x-2">
|
||||
<GitBranch className="w-4 h-4" />
|
||||
<span>仓库审计</span>
|
||||
<span className="font-mono">仓库审计</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="instant">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Zap className="w-4 h-4" />
|
||||
<span>即时分析</span>
|
||||
<span className="font-mono">即时分析</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
|
@ -516,44 +521,43 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
|
||||
{taskForm.task_type === "repository" && (selectedProject.repository_url) && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="branch_name">目标分支</Label>
|
||||
<Label htmlFor="branch_name" className="font-bold uppercase">目标分支</Label>
|
||||
<Input
|
||||
id="branch_name"
|
||||
value={taskForm.branch_name || ""}
|
||||
onChange={(e) => setTaskForm({ ...taskForm, branch_name: e.target.value })}
|
||||
placeholder={selectedProject.default_branch || "main"}
|
||||
className="retro-input h-10"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 项目信息展示 */}
|
||||
<Card className="bg-blue-50 border-blue-200">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium text-blue-900 mb-1">选中项目:{selectedProject.name}</p>
|
||||
<div className="text-blue-700 space-y-1">
|
||||
{selectedProject.description && (
|
||||
<p>描述:{selectedProject.description}</p>
|
||||
)}
|
||||
<p>默认分支:{selectedProject.default_branch}</p>
|
||||
{selectedProject.programming_languages && (
|
||||
<p>编程语言:{JSON.parse(selectedProject.programming_languages).join(', ')}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-blue-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
|
||||
<div className="text-sm font-mono">
|
||||
<p className="font-bold text-blue-900 mb-1 uppercase">选中项目:{selectedProject.name}</p>
|
||||
<div className="text-blue-800 space-y-1 font-bold">
|
||||
{selectedProject.description && (
|
||||
<p>描述:{selectedProject.description}</p>
|
||||
)}
|
||||
<p>默认分支:{selectedProject.default_branch}</p>
|
||||
{selectedProject.programming_languages && (
|
||||
<p>编程语言:{JSON.parse(selectedProject.programming_languages).join(', ')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="exclude" className="space-y-4 mt-6">
|
||||
<TabsContent value="exclude" className="space-y-4 mt-6 font-mono">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-base font-medium">排除模式</Label>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
<Label className="text-base font-bold uppercase">排除模式</Label>
|
||||
<p className="text-sm text-gray-500 mt-1 font-bold">
|
||||
选择要从审计中排除的文件和目录模式
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -561,14 +565,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
{/* 常用排除模式 */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{commonExcludePatterns.map((pattern) => (
|
||||
<div key={pattern.value} className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50">
|
||||
<div key={pattern.value} className="flex items-center space-x-3 p-3 border-2 border-black bg-white hover:bg-gray-50 transition-all">
|
||||
<Checkbox
|
||||
checked={taskForm.exclude_patterns.includes(pattern.value)}
|
||||
onCheckedChange={() => toggleExcludePattern(pattern.value)}
|
||||
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{pattern.label}</p>
|
||||
<p className="text-xs text-gray-500">{pattern.description}</p>
|
||||
<p className="text-sm font-bold uppercase">{pattern.label}</p>
|
||||
<p className="text-xs text-gray-500 font-bold">{pattern.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -576,7 +581,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
|
||||
{/* 自定义排除模式 */}
|
||||
<div className="space-y-2">
|
||||
<Label>自定义排除模式</Label>
|
||||
<Label className="font-bold uppercase">自定义排除模式</Label>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
placeholder="例如: *.tmp, test/**"
|
||||
|
|
@ -586,6 +591,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
e.currentTarget.value = '';
|
||||
}
|
||||
}}
|
||||
className="retro-input h-10"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -595,6 +601,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
addCustomPattern(input.value);
|
||||
input.value = '';
|
||||
}}
|
||||
className="retro-btn bg-white text-black h-10"
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
|
|
@ -604,13 +611,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
{/* 已选择的排除模式 */}
|
||||
{taskForm.exclude_patterns.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>已选择的排除模式</Label>
|
||||
<Label className="font-bold uppercase">已选择的排除模式</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{taskForm.exclude_patterns.map((pattern) => (
|
||||
<Badge
|
||||
key={pattern}
|
||||
variant="secondary"
|
||||
className="cursor-pointer hover:bg-red-100 hover:text-red-800"
|
||||
className="cursor-pointer hover:bg-red-100 hover:text-red-800 rounded-none border-2 border-black bg-gray-100 text-black font-mono font-bold"
|
||||
onClick={() => removeExcludePattern(pattern)}
|
||||
>
|
||||
{pattern} ×
|
||||
|
|
@ -622,18 +629,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="advanced" className="space-y-4 mt-6">
|
||||
<TabsContent value="advanced" className="space-y-4 mt-6 font-mono">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<Label className="text-base font-medium">扫描配置</Label>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
<Label className="text-base font-bold uppercase">扫描配置</Label>
|
||||
<p className="text-sm text-gray-500 mt-1 font-bold">
|
||||
配置代码扫描的详细参数
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-3 p-3 border-2 border-black bg-white">
|
||||
<Checkbox
|
||||
checked={taskForm.scan_config.include_tests}
|
||||
onCheckedChange={(checked) =>
|
||||
|
|
@ -642,14 +649,15 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
scan_config: { ...taskForm.scan_config, include_tests: !!checked }
|
||||
})
|
||||
}
|
||||
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
|
||||
/>
|
||||
<div>
|
||||
<p className="text-sm font-medium">包含测试文件</p>
|
||||
<p className="text-xs text-gray-500">扫描 *test*, *spec* 等测试文件</p>
|
||||
<p className="text-sm font-bold uppercase">包含测试文件</p>
|
||||
<p className="text-xs text-gray-500 font-bold">扫描 *test*, *spec* 等测试文件</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-3 p-3 border-2 border-black bg-white">
|
||||
<Checkbox
|
||||
checked={taskForm.scan_config.include_docs}
|
||||
onCheckedChange={(checked) =>
|
||||
|
|
@ -658,17 +666,18 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
scan_config: { ...taskForm.scan_config, include_docs: !!checked }
|
||||
})
|
||||
}
|
||||
className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white"
|
||||
/>
|
||||
<div>
|
||||
<p className="text-sm font-medium">包含文档文件</p>
|
||||
<p className="text-xs text-gray-500">扫描 README, docs 等文档文件</p>
|
||||
<p className="text-sm font-bold uppercase">包含文档文件</p>
|
||||
<p className="text-xs text-gray-500 font-bold">扫描 README, docs 等文档文件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="max_file_size">最大文件大小 (KB)</Label>
|
||||
<Label htmlFor="max_file_size" className="font-bold uppercase">最大文件大小 (KB)</Label>
|
||||
<Input
|
||||
id="max_file_size"
|
||||
type="number"
|
||||
|
|
@ -684,11 +693,12 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
}
|
||||
min="1"
|
||||
max="10240"
|
||||
className="retro-input h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="analysis_depth">分析深度</Label>
|
||||
<Label htmlFor="analysis_depth" className="font-bold uppercase">分析深度</Label>
|
||||
<Select
|
||||
value={taskForm.scan_config.analysis_depth}
|
||||
onValueChange={(value: any) =>
|
||||
|
|
@ -698,13 +708,13 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="retro-input h-10 rounded-none border-2 border-black shadow-none focus:ring-0">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="basic">基础扫描</SelectItem>
|
||||
<SelectItem value="standard">标准扫描</SelectItem>
|
||||
<SelectItem value="deep">深度扫描</SelectItem>
|
||||
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<SelectItem value="basic" className="font-mono">基础扫描</SelectItem>
|
||||
<SelectItem value="standard" className="font-mono">标准扫描</SelectItem>
|
||||
<SelectItem value="deep" className="font-mono">深度扫描</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
|
@ -712,39 +722,42 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr
|
|||
</div>
|
||||
|
||||
{/* 分析深度说明 */}
|
||||
<Card className="bg-amber-50 border-amber-200">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium text-amber-900 mb-2">分析深度说明:</p>
|
||||
<ul className="text-amber-800 space-y-1 text-xs">
|
||||
<li>• <strong>基础扫描</strong>:快速检查语法错误和基本问题</li>
|
||||
<li>• <strong>标准扫描</strong>:包含代码质量、安全性和性能分析</li>
|
||||
<li>• <strong>深度扫描</strong>:全面分析,包含复杂度、可维护性等高级指标</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-amber-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-start space-x-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div className="text-sm font-mono">
|
||||
<p className="font-bold text-amber-900 mb-2 uppercase">分析深度说明:</p>
|
||||
<ul className="text-amber-800 space-y-1 text-xs font-bold">
|
||||
<li>• <strong>基础扫描</strong>:快速检查语法错误和基本问题</li>
|
||||
<li>• <strong>标准扫描</strong>:包含代码质量、安全性和性能分析</li>
|
||||
<li>• <strong>深度扫描</strong>:全面分析,包含复杂度、可维护性等高级指标</li>
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex justify-end space-x-3 pt-4 border-t">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={creating}>
|
||||
<div className="flex justify-end space-x-3 pt-6 border-t-2 border-black bg-gray-50 -mx-6 -mb-6 p-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={creating}
|
||||
className="retro-btn bg-white text-black h-12 px-6 font-bold uppercase"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateTask}
|
||||
disabled={!taskForm.project_id || creating}
|
||||
className="btn-primary"
|
||||
className="retro-btn bg-primary text-white h-12 px-6 font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]"
|
||||
>
|
||||
{creating ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
<div className="animate-spin rounded-none h-4 w-4 border-2 border-white border-t-transparent mr-2"></div>
|
||||
创建中...
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { Dialog, DialogOverlay, DialogPortal } from "@/components/ui/dialog";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { Terminal, CheckCircle, XCircle, Loader2, X as XIcon } from "lucide-react";
|
||||
import { Terminal, X as XIcon } from "lucide-react";
|
||||
import { cn, calculateTaskProgress } from "@/shared/utils/utils";
|
||||
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
||||
import { taskControl } from "@/shared/services/taskControl";
|
||||
|
|
@ -112,8 +112,8 @@ export default function TerminalProgressDialog({
|
|||
|
||||
// 初始化日志
|
||||
addLog("🚀 审计任务已启动", "info");
|
||||
addLog(`<EFBFBD> 任务任ID: ${taskId}`, "info");
|
||||
addLog(`<EFBFBD> 任务类D型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
|
||||
addLog(`任务ID: ${taskId}`, "info");
|
||||
addLog(`任务类型: ${taskType === "repository" ? "仓库审计" : "ZIP文件审计"}`, "info");
|
||||
addLog("⏳ 正在初始化审计环境...", "info");
|
||||
}
|
||||
|
||||
|
|
@ -383,42 +383,31 @@ export default function TerminalProgressDialog({
|
|||
const getLogColor = (type: LogEntry["type"]) => {
|
||||
switch (type) {
|
||||
case "success":
|
||||
return "text-emerald-400";
|
||||
return "text-green-500";
|
||||
case "error":
|
||||
return "text-rose-400";
|
||||
return "text-red-500";
|
||||
case "warning":
|
||||
return "text-amber-400";
|
||||
return "text-yellow-500";
|
||||
default:
|
||||
return "text-gray-200";
|
||||
return "text-gray-300";
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态图标
|
||||
const getStatusIcon = () => {
|
||||
if (isFailed) {
|
||||
return <XCircle className="w-5 h-5 text-rose-400" />;
|
||||
}
|
||||
if (isCompleted) {
|
||||
return <CheckCircle className="w-5 h-5 text-emerald-400" />;
|
||||
}
|
||||
return <Loader2 className="w-5 h-5 text-rose-400 animate-spin" />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogOverlay className="bg-black/50 backdrop-blur-sm" />
|
||||
<DialogPrimitive.Content
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
|
||||
"w-[90vw] aspect-[16/9]",
|
||||
"max-w-[1600px] max-h-[900px]",
|
||||
"p-0 gap-0 rounded-lg overflow-hidden",
|
||||
"bg-gradient-to-br from-gray-900 via-red-950/30 to-gray-900 border border-red-900/50",
|
||||
"max-w-[1200px] max-h-[800px]",
|
||||
"p-0 gap-0 rounded-none overflow-hidden",
|
||||
"bg-black border-4 border-gray-500 shadow-[10px_10px_0px_0px_rgba(0,0,0,0.5)]",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
||||
"duration-200 shadow-2xl"
|
||||
"duration-200"
|
||||
)}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
|
|
@ -432,33 +421,46 @@ export default function TerminalProgressDialog({
|
|||
</VisuallyHidden.Root>
|
||||
|
||||
{/* 终端头部 */}
|
||||
<div className="flex items-center justify-between px-4 py-3 bg-gradient-to-r from-red-950/50 to-gray-900/80 border-b border-red-900/30 backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-gray-300 border-b-4 border-gray-500">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Terminal className="w-5 h-5 text-rose-400" />
|
||||
<span className="text-sm font-medium text-gray-100">审计进度监控</span>
|
||||
{getStatusIcon()}
|
||||
<Terminal className="w-5 h-5 text-black" />
|
||||
<span className="text-sm font-bold text-black uppercase font-display tracking-wider">TERMINAL // 审计进度监控</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500" />
|
||||
<div className="w-3 h-3 rounded-full bg-amber-500" />
|
||||
{/* 模拟窗口控制按钮 */}
|
||||
<div className="w-4 h-4 border-2 border-black bg-white flex items-center justify-center">
|
||||
<div className="w-2 h-0.5 bg-black"></div>
|
||||
</div>
|
||||
<div className="w-4 h-4 border-2 border-black bg-white flex items-center justify-center">
|
||||
<div className="w-2 h-2 border border-black"></div>
|
||||
</div>
|
||||
<button
|
||||
className="w-3 h-3 rounded-full bg-rose-500 hover:bg-rose-600 cursor-pointer transition-colors focus:outline-none"
|
||||
className="w-4 h-4 border-2 border-black bg-primary hover:bg-red-600 cursor-pointer transition-colors focus:outline-none flex items-center justify-center"
|
||||
onClick={() => onOpenChange(false)}
|
||||
title="关闭"
|
||||
aria-label="关闭"
|
||||
/>
|
||||
>
|
||||
<XIcon className="w-3 h-3 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 终端内容 */}
|
||||
<div className="p-6 bg-gradient-to-b from-gray-900/95 to-gray-950/95 overflow-y-auto h-[calc(100%-120px)] font-mono text-sm backdrop-blur-sm">
|
||||
<div className="space-y-2">
|
||||
<div className="p-6 bg-black overflow-y-auto h-[calc(100%-100px)] font-mono text-sm relative">
|
||||
{/* 扫描线效果 */}
|
||||
<div className="absolute inset-0 pointer-events-none bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] z-10 bg-[length:100%_2px,3px_100%]"></div>
|
||||
|
||||
<div className="space-y-1 relative z-20">
|
||||
{logs.map((log, index) => (
|
||||
<div key={index} className="flex items-start space-x-3 hover:bg-red-950/10 px-2 py-1 rounded transition-colors">
|
||||
<span className="text-rose-800/70 text-xs flex-shrink-0 w-20">
|
||||
<div key={index} className="flex items-start space-x-3 hover:bg-white/5 px-2 py-0.5 transition-colors">
|
||||
<span className="text-gray-500 text-xs flex-shrink-0 w-24 font-bold">
|
||||
[{log.timestamp}]
|
||||
</span>
|
||||
<span className={`${getLogColor(log.type)} flex-1`}>
|
||||
<span className={`${getLogColor(log.type)} flex-1 font-bold tracking-wide`}>
|
||||
{log.type === 'info' && '> '}
|
||||
{log.type === 'success' && '✓ '}
|
||||
{log.type === 'error' && '✗ '}
|
||||
{log.type === 'warning' && '! '}
|
||||
{log.message}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -466,107 +468,63 @@ export default function TerminalProgressDialog({
|
|||
|
||||
{/* 光标旋转闪烁效果 */}
|
||||
{!isCompleted && !isFailed && (
|
||||
<div className="flex items-center space-x-2 mt-4">
|
||||
<span className="text-rose-800/70 text-xs w-20">[{currentTime}]</span>
|
||||
<span className="inline-block text-rose-400 animate-spinner font-bold text-base"></span>
|
||||
<div className="flex items-center space-x-2 mt-4 px-2">
|
||||
<span className="text-gray-500 text-xs w-24 font-bold">[{currentTime}]</span>
|
||||
<span className="inline-block text-green-500 animate-pulse font-bold text-base">_</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 添加自定义动画 */}
|
||||
<style>{`
|
||||
@keyframes spinner {
|
||||
0% {
|
||||
content: '|';
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
content: '/';
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
content: '—';
|
||||
opacity: 0.6;
|
||||
}
|
||||
75% {
|
||||
content: '\\\\';
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
content: '|';
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.animate-spinner::before {
|
||||
content: '|';
|
||||
animation: spinner-content 0.8s linear infinite;
|
||||
}
|
||||
.animate-spinner {
|
||||
animation: spinner-opacity 0.8s ease-in-out infinite;
|
||||
}
|
||||
@keyframes spinner-content {
|
||||
0% { content: '|'; }
|
||||
25% { content: '/'; }
|
||||
50% { content: '—'; }
|
||||
75% { content: '\\\\'; }
|
||||
100% { content: '|'; }
|
||||
}
|
||||
@keyframes spinner-opacity {
|
||||
0%, 100% { opacity: 1; }
|
||||
25%, 75% { opacity: 0.8; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div ref={logsEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部控制和提示 */}
|
||||
<div className="px-4 py-3 bg-gradient-to-r from-red-950/50 to-gray-900/80 border-t border-red-900/30 backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-300">
|
||||
{isCancelled ? "🛑 任务已取消,已分析的结果已保存" :
|
||||
isCompleted ? "✅ 任务已完成,可以关闭此窗口" :
|
||||
isFailed ? "❌ 任务失败,请检查配置后重试" :
|
||||
"⏳ 审计进行中,请勿关闭窗口,过程可能较慢,请耐心等待......"}
|
||||
<div className="px-4 py-3 bg-gray-200 border-t-4 border-gray-500 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className={`w-3 h-3 border-2 border-black ${isFailed ? 'bg-red-500' : isCompleted ? 'bg-green-500' : 'bg-yellow-400 animate-pulse'}`}></div>
|
||||
<span className="text-xs font-bold text-black uppercase font-mono tracking-tight">
|
||||
{isCancelled ? "STATUS: CANCELLED // 任务已取消" :
|
||||
isCompleted ? "STATUS: COMPLETED // 任务已完成" :
|
||||
isFailed ? "STATUS: FAILED // 任务失败" :
|
||||
"STATUS: RUNNING // 审计进行中..."}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* 运行中显示取消按钮 */}
|
||||
{!isCompleted && !isFailed && !isCancelled && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
className="h-7 text-xs bg-gray-800 border-red-600 text-red-400 hover:bg-red-900 hover:text-red-200"
|
||||
>
|
||||
<XIcon className="w-3 h-3 mr-1" />
|
||||
取消任务
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex items-center space-x-3">
|
||||
{/* 运行中显示取消按钮 */}
|
||||
{!isCompleted && !isFailed && !isCancelled && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
className="h-8 text-xs bg-white border-2 border-black text-black hover:bg-red-100 hover:text-red-900 font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none"
|
||||
>
|
||||
<XIcon className="w-3 h-3 mr-1" />
|
||||
取消任务
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* 失败时显示查看日志按钮 */}
|
||||
{isFailed && (
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open('/logs', '_blank');
|
||||
}}
|
||||
className="px-4 py-1.5 bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-500 hover:to-orange-500 text-white rounded text-xs transition-all shadow-lg shadow-yellow-900/50 font-medium"
|
||||
>
|
||||
📋 查看日志
|
||||
</button>
|
||||
)}
|
||||
{/* 失败时显示查看日志按钮 */}
|
||||
{isFailed && (
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open('/logs', '_blank');
|
||||
}}
|
||||
className="px-4 py-1.5 bg-yellow-400 border-2 border-black text-black hover:bg-yellow-500 text-xs font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none transition-all"
|
||||
>
|
||||
📋 查看日志
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 已完成/失败/取消显示关闭按钮 */}
|
||||
{(isCompleted || isFailed || isCancelled) && (
|
||||
<button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="px-4 py-1.5 bg-gradient-to-r from-rose-600 to-red-600 hover:from-rose-500 hover:to-red-500 text-white rounded text-xs transition-all shadow-lg shadow-rose-900/50 font-medium"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* 已完成/失败/取消显示关闭按钮 */}
|
||||
{(isCompleted || isFailed || isCancelled) && (
|
||||
<button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="px-4 py-1.5 bg-primary border-2 border-black text-white hover:bg-primary/90 text-xs font-bold uppercase rounded-none shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none transition-all"
|
||||
>
|
||||
关闭窗口
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogPrimitive.Content>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import {
|
|||
FileText,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Github
|
||||
Github,
|
||||
Terminal
|
||||
} from "lucide-react";
|
||||
import routes from "@/app/routes";
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="fixed top-4 left-4 z-50 md:hidden bg-white shadow-md border border-gray-200 text-gray-700 hover:bg-gray-50"
|
||||
className="fixed top-4 left-4 z-50 md:hidden bg-white border-2 border-black shadow-retro text-black hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-retro-hover transition-all"
|
||||
onClick={() => setMobileOpen(!mobileOpen)}
|
||||
>
|
||||
{mobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||||
|
|
@ -54,7 +55,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
|||
{/* Overlay for mobile */}
|
||||
{mobileOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 backdrop-blur-sm z-40 md:hidden transition-opacity"
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 md:hidden"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -62,41 +63,37 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
|||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`
|
||||
fixed top-0 left-0 h-screen bg-white/95 backdrop-blur-xl
|
||||
border-r border-gray-200/80 shadow-[4px_0_24px_-12px_rgba(0,0,0,0.1)]
|
||||
fixed top-0 left-0 h-screen bg-background
|
||||
border-r-2 border-black
|
||||
z-40 transition-all duration-300 ease-in-out
|
||||
${collapsed ? "w-20" : "w-64"}
|
||||
${mobileOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex flex-col h-full relative">
|
||||
{/* Decorative Grid Background */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||
|
||||
{/* Logo Section */}
|
||||
<div className="relative flex items-center h-[72px] px-4 border-b border-gray-100">
|
||||
<div className="relative flex items-center h-[72px] px-4 border-b-2 border-black bg-white z-10">
|
||||
<Link
|
||||
to="/"
|
||||
className={`flex items-center space-x-3 group overflow-hidden transition-all duration-300 ${collapsed ? 'justify-center w-full' : ''}`}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
<div className="relative flex-shrink-0">
|
||||
<div className="absolute inset-0 bg-red-500/20 blur-lg rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
<img
|
||||
src="/logo_xcodereviewer.png"
|
||||
alt="XCodeReviewer Logo"
|
||||
className="w-9 h-9 rounded-xl shadow-sm relative z-10 group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<div className="relative flex-shrink-0 border-2 border-black bg-primary p-1 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Terminal className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className={`transition-all duration-300 ${collapsed ? 'w-0 opacity-0 overflow-hidden' : 'w-auto opacity-100'}`}>
|
||||
<span className="text-lg font-bold bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-700 group-hover:from-red-700 group-hover:to-red-500 whitespace-nowrap">
|
||||
XCodeReviewer
|
||||
<span className="text-lg font-display font-bold text-black tracking-tighter uppercase">
|
||||
XCode<span className="text-primary">Reviewer</span>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* Collapse button for desktop - Absolute Positioned */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white text-gray-400 hover:text-gray-900 border border-gray-200 shadow-sm z-50 hover:bg-gray-50"
|
||||
{/* Collapse button for desktop */}
|
||||
<button
|
||||
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 bg-white border-2 border-black items-center justify-center hover:bg-primary hover:text-white transition-colors z-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-none"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
>
|
||||
{collapsed ? (
|
||||
|
|
@ -104,12 +101,12 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
|||
) : (
|
||||
<ChevronLeft className="w-3 h-3" />
|
||||
)}
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 overflow-y-auto py-6 px-3">
|
||||
<div className="space-y-1.5">
|
||||
<nav className="flex-1 overflow-y-auto py-6 px-3 z-10">
|
||||
<div className="space-y-2">
|
||||
{visibleRoutes.map((route) => {
|
||||
const isActive = location.pathname === route.path;
|
||||
return (
|
||||
|
|
@ -117,39 +114,37 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
|||
key={route.path}
|
||||
to={route.path}
|
||||
className={`
|
||||
flex items-center space-x-3 px-3 py-2.5 rounded-xl
|
||||
transition-all duration-200 group relative overflow-hidden
|
||||
flex items-center space-x-3 px-3 py-3
|
||||
transition-all duration-200 group relative
|
||||
border-2
|
||||
${isActive
|
||||
? "bg-red-50 text-red-700 font-medium shadow-sm ring-1 ring-red-100"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
||||
? "bg-primary border-black shadow-retro text-white"
|
||||
: "bg-transparent border-transparent hover:border-black hover:bg-white hover:shadow-retro-hover text-gray-600 hover:text-black"
|
||||
}
|
||||
`}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
title={collapsed ? route.name : undefined}
|
||||
>
|
||||
{/* Active Indicator Bar */}
|
||||
{isActive && (
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-red-600 rounded-r-full opacity-0 md:opacity-100 transition-opacity" />
|
||||
)}
|
||||
|
||||
{/* Icon */}
|
||||
<span className={`
|
||||
flex-shrink-0 transition-colors duration-200
|
||||
${isActive ? "text-red-600" : "text-gray-400 group-hover:text-gray-600"}
|
||||
${isActive ? "text-white" : "text-black group-hover:text-black"}
|
||||
`}>
|
||||
{routeIcons[route.path] || <LayoutDashboard className="w-5 h-5" />}
|
||||
</span>
|
||||
|
||||
{/* Label */}
|
||||
{!collapsed && (
|
||||
<span className="whitespace-nowrap z-10">
|
||||
<span className="font-mono font-bold tracking-tight uppercase text-sm">
|
||||
{route.name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Hover Effect Background (Subtle) */}
|
||||
{!isActive && (
|
||||
<div className="absolute inset-0 bg-gray-100 opacity-0 group-hover:opacity-100 transition-opacity duration-200 -z-0 rounded-xl" />
|
||||
{/* Glitch Effect on Hover (Optional) */}
|
||||
{!isActive && !collapsed && (
|
||||
<span className="absolute right-2 opacity-0 group-hover:opacity-100 text-xs font-bold text-primary animate-pulse">
|
||||
</>
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
|
|
@ -158,23 +153,23 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
|
|||
</nav>
|
||||
|
||||
{/* Footer with GitHub Link */}
|
||||
<div className="p-4 border-t border-gray-100 bg-gray-50/50">
|
||||
<div className="p-4 border-t-2 border-black bg-white z-10">
|
||||
<a
|
||||
href="https://github.com/lintsinghua/XCodeReviewer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`
|
||||
flex items-center space-x-3 px-3 py-2.5 rounded-xl
|
||||
text-gray-500 hover:bg-white hover:text-gray-900 hover:shadow-sm hover:ring-1 hover:ring-gray-200
|
||||
flex items-center space-x-3 px-3 py-2.5
|
||||
text-black border-2 border-transparent hover:border-black hover:shadow-retro-hover hover:bg-white
|
||||
transition-all duration-200 group
|
||||
`}
|
||||
title={collapsed ? "GitHub Repository" : undefined}
|
||||
title={collapsed ? "GitHub 仓库" : undefined}
|
||||
>
|
||||
<Github className="w-5 h-5 flex-shrink-0 text-gray-400 group-hover:text-gray-900 transition-colors" />
|
||||
<Github className="w-5 h-5 flex-shrink-0" />
|
||||
{!collapsed && (
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-sm">GitHub</span>
|
||||
<span className="text-xs text-gray-400 group-hover:text-gray-500">View Source</span>
|
||||
<span className="font-mono font-bold text-sm uppercase">GitHub</span>
|
||||
<span className="text-xs text-gray-500 font-mono">v1.0.0</span>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -78,18 +78,18 @@ export default function ExportReportDialog({
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center space-x-2">
|
||||
<Download className="w-5 h-5 text-primary" />
|
||||
<DialogContent className="sm:max-w-[600px] bg-white border-2 border-black p-0 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none">
|
||||
<DialogHeader className="p-6 border-b-2 border-black bg-gray-50">
|
||||
<DialogTitle className="flex items-center space-x-2 font-display font-bold uppercase text-xl">
|
||||
<Download className="w-6 h-6 text-black" />
|
||||
<span>导出审计报告</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<DialogDescription className="font-mono text-xs text-gray-500 mt-2">
|
||||
选择报告格式并导出完整的代码审计结果
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-6">
|
||||
<div className="p-6">
|
||||
<RadioGroup
|
||||
value={selectedFormat}
|
||||
onValueChange={(value) => setSelectedFormat(value as ExportFormat)}
|
||||
|
|
@ -108,29 +108,27 @@ export default function ExportReportDialog({
|
|||
/>
|
||||
<Label
|
||||
htmlFor={format.value}
|
||||
className={`flex items-start space-x-4 p-4 rounded-lg border-2 cursor-pointer transition-all ${isSelected
|
||||
? `${format.borderColor} ${format.bgColor} shadow-md`
|
||||
: "border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"
|
||||
className={`flex items-start space-x-4 p-4 border-2 cursor-pointer transition-all rounded-none font-mono ${isSelected
|
||||
? "border-black bg-gray-100 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] translate-x-[-2px] translate-y-[-2px]"
|
||||
: "border-black bg-white hover:bg-gray-50 hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-12 h-12 rounded-lg flex items-center justify-center ${isSelected ? format.bgColor : "bg-gray-50"
|
||||
className={`w-12 h-12 border-2 border-black flex items-center justify-center rounded-none ${isSelected ? "bg-black text-white" : "bg-white text-black"
|
||||
}`}
|
||||
>
|
||||
<Icon className={`w-6 h-6 ${isSelected ? format.color : "text-gray-400"}`} />
|
||||
<Icon className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<h4 className={`font-semibold ${isSelected ? format.color : "text-gray-900"}`}>
|
||||
<h4 className="font-bold uppercase text-black">
|
||||
{format.label}
|
||||
</h4>
|
||||
{isSelected && (
|
||||
<div className={`w-5 h-5 rounded-full ${format.bgColor} flex items-center justify-center`}>
|
||||
<div className={`w-2.5 h-2.5 rounded-full ${format.color.replace("text-", "bg-")}`} />
|
||||
</div>
|
||||
<div className="w-4 h-4 bg-black border-2 border-black" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">{format.description}</p>
|
||||
<p className="text-xs text-gray-600 font-bold">{format.description}</p>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
|
|
@ -139,32 +137,32 @@ export default function ExportReportDialog({
|
|||
</RadioGroup>
|
||||
|
||||
{/* 报告预览信息 */}
|
||||
<div className="mt-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<h4 className="font-medium text-gray-900 mb-3">报告内容预览</h4>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">项目名称:</span>
|
||||
<span className="font-medium text-gray-900">{task.project?.name || "未知"}</span>
|
||||
<div className="mt-6 p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<h4 className="font-bold text-black uppercase mb-3 font-display border-b-2 border-black pb-2 w-fit">报告内容预览</h4>
|
||||
<div className="grid grid-cols-2 gap-3 text-xs font-mono">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">项目名称:</span>
|
||||
<span className="font-bold text-black">{task.project?.name || "未知"}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">质量评分:</span>
|
||||
<span className="font-medium text-gray-900">{task.quality_score.toFixed(1)}/100</span>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">质量评分:</span>
|
||||
<span className="font-bold text-black">{task.quality_score.toFixed(1)}/100</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">扫描文件:</span>
|
||||
<span className="font-medium text-gray-900">{task.scanned_files}/{task.total_files}</span>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">扫描文件:</span>
|
||||
<span className="font-bold text-black">{task.scanned_files}/{task.total_files}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">发现问题:</span>
|
||||
<span className="font-medium text-orange-600">{issues.length}</span>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">发现问题:</span>
|
||||
<span className="font-bold text-orange-600">{issues.length}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">代码行数:</span>
|
||||
<span className="font-medium text-gray-900">{task.total_lines.toLocaleString()}</span>
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">代码行数:</span>
|
||||
<span className="font-bold text-black">{task.total_lines.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-600">严重问题:</span>
|
||||
<span className="font-medium text-red-600">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-1">
|
||||
<span className="text-gray-600 font-bold">严重问题:</span>
|
||||
<span className="font-bold text-red-600">
|
||||
{issues.filter(i => i.severity === "critical").length}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -172,18 +170,19 @@ export default function ExportReportDialog({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogFooter className="p-6 border-t-2 border-black bg-gray-50 flex justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isExporting}
|
||||
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleExport}
|
||||
disabled={isExporting}
|
||||
className="bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800"
|
||||
className="retro-btn bg-primary text-white border-2 border-black hover:bg-primary/90 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
|
||||
>
|
||||
{isExporting ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Settings,
|
||||
|
|
@ -310,8 +308,8 @@ export function SystemConfig() {
|
|||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">正在从后端加载配置...</p>
|
||||
<div className="animate-spin rounded-none h-12 w-12 border-4 border-black border-t-transparent mx-auto mb-4"></div>
|
||||
<p className="text-black font-mono font-bold uppercase">正在从后端加载配置...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -320,98 +318,98 @@ export function SystemConfig() {
|
|||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 配置状态提示 */}
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription className="flex items-center justify-between">
|
||||
<div>
|
||||
<strong>当前配置来源:</strong>
|
||||
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div className="flex-1 flex items-center justify-between">
|
||||
<div className="font-mono text-sm text-blue-800">
|
||||
<strong className="uppercase">当前配置来源:</strong>
|
||||
{configSource === 'runtime' ? (
|
||||
<Badge variant="default" className="ml-2">运行时配置</Badge>
|
||||
<Badge variant="default" className="ml-2 rounded-none border-blue-800 bg-blue-600 text-white font-bold uppercase">运行时配置</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="ml-2">构建时配置</Badge>
|
||||
<Badge variant="outline" className="ml-2 rounded-none border-blue-800 text-blue-800 font-bold uppercase">构建时配置</Badge>
|
||||
)}
|
||||
<span className="ml-4 text-sm">
|
||||
<span className="ml-4 text-sm font-bold">
|
||||
{isConfigured ? (
|
||||
<span className="text-green-600 flex items-center gap-1">
|
||||
<CheckCircle2 className="h-3 w-3" /> LLM 已配置
|
||||
<CheckCircle2 className="h-4 w-4" /> LLM 已配置
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-orange-600 flex items-center gap-1">
|
||||
<AlertCircle className="h-3 w-3" /> 未配置 LLM
|
||||
<AlertCircle className="h-4 w-4" /> 未配置 LLM
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{hasChanges && (
|
||||
<Button onClick={saveConfig} size="sm">
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
<Button onClick={saveConfig} size="sm" className="retro-btn bg-black text-white border-2 border-black hover:bg-gray-800 rounded-none h-8 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]">
|
||||
<Save className="w-3 h-3 mr-2" />
|
||||
保存配置
|
||||
</Button>
|
||||
)}
|
||||
{configSource === 'runtime' && (
|
||||
<Button onClick={resetConfig} variant="outline" size="sm">
|
||||
<RotateCcw className="w-4 h-4 mr-2" />
|
||||
<Button onClick={resetConfig} variant="outline" size="sm" className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-8 font-bold uppercase">
|
||||
<RotateCcw className="w-3 h-3 mr-2" />
|
||||
重置
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="llm" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="llm">
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
<TabsList className="grid w-full grid-cols-4 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6">
|
||||
<TabsTrigger value="llm" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
<Zap className="w-3 h-3 mr-2" />
|
||||
LLM 配置
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="platforms">
|
||||
<Key className="w-4 h-4 mr-2" />
|
||||
<TabsTrigger value="platforms" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
<Key className="w-3 h-3 mr-2" />
|
||||
平台密钥
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="analysis">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
<TabsTrigger value="analysis" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
<Settings className="w-3 h-3 mr-2" />
|
||||
分析参数
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="other">
|
||||
<Globe className="w-4 h-4 mr-2" />
|
||||
<TabsTrigger value="other" className="rounded-none data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
<Globe className="w-3 h-3 mr-2" />
|
||||
其他配置
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* LLM 基础配置 */}
|
||||
<TabsContent value="llm" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>LLM 提供商配置</CardTitle>
|
||||
<CardDescription>选择和配置大语言模型服务</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">LLM 提供商配置</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">选择和配置大语言模型服务</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="space-y-2">
|
||||
<Label>当前使用的 LLM 提供商</Label>
|
||||
<Label className="font-bold uppercase">当前使用的 LLM 提供商</Label>
|
||||
<Select
|
||||
value={config.llmProvider}
|
||||
onValueChange={(value) => updateConfig('llmProvider', value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="retro-input h-10 bg-gray-50 border-2 border-black text-black focus:ring-0 focus:border-primary rounded-none font-mono">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground">国际平台</div>
|
||||
<SelectContent className="retro-card border-2 border-black rounded-none">
|
||||
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase">国际平台</div>
|
||||
{LLM_PROVIDERS.filter(p => p.category === 'international').map(provider => (
|
||||
<SelectItem key={provider.value} value={provider.value}>
|
||||
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
|
||||
{provider.icon} {provider.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground mt-2">国内平台</div>
|
||||
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase mt-2">国内平台</div>
|
||||
{LLM_PROVIDERS.filter(p => p.category === 'domestic').map(provider => (
|
||||
<SelectItem key={provider.value} value={provider.value}>
|
||||
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
|
||||
{provider.icon} {provider.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
<div className="px-2 py-1.5 text-sm font-semibold text-muted-foreground mt-2">本地部署</div>
|
||||
<div className="px-2 py-1.5 text-xs font-bold text-gray-500 uppercase mt-2">本地部署</div>
|
||||
{LLM_PROVIDERS.filter(p => p.category === 'local').map(provider => (
|
||||
<SelectItem key={provider.value} value={provider.value}>
|
||||
<SelectItem key={provider.value} value={provider.value} className="font-mono focus:bg-primary/20 focus:text-black">
|
||||
{provider.icon} {provider.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -420,58 +418,62 @@ export function SystemConfig() {
|
|||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>通用 API Key(可选)</Label>
|
||||
<Label className="font-bold uppercase">通用 API Key(可选)</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
type={showApiKeys['llm'] ? 'text' : 'password'}
|
||||
value={config.llmApiKey}
|
||||
onChange={(e) => updateConfig('llmApiKey', e.target.value)}
|
||||
placeholder="留空则使用平台专用 API Key"
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => toggleShowApiKey('llm')}
|
||||
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
|
||||
>
|
||||
{showApiKeys['llm'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
如果设置,将优先使用此 API Key;否则使用下方对应平台的专用 API Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>模型名称(可选)</Label>
|
||||
<Label className="font-bold uppercase">模型名称(可选)</Label>
|
||||
<Input
|
||||
value={config.llmModel}
|
||||
onChange={(e) => updateConfig('llmModel', e.target.value)}
|
||||
placeholder={`默认:${DEFAULT_MODELS[config.llmProvider as keyof typeof DEFAULT_MODELS] || '自动'}`}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
留空使用默认模型
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>API 基础 URL(推荐配置)</Label>
|
||||
<Label className="font-bold uppercase">API 基础 URL(推荐配置)</Label>
|
||||
<Input
|
||||
value={config.llmBaseUrl}
|
||||
onChange={(e) => updateConfig('llmBaseUrl', e.target.value)}
|
||||
placeholder="例如:https://api.example.com/v1"
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<div className="text-xs text-gray-500 font-mono space-y-1">
|
||||
<p>💡 <strong>使用 API 中转站?</strong>在这里填入中转站地址。配置保存后会在实际使用时自动验证。</p>
|
||||
<details className="cursor-pointer">
|
||||
<summary className="text-primary hover:underline">查看常见 API 中转示例</summary>
|
||||
<div className="mt-2 p-3 bg-muted rounded space-y-1 text-xs">
|
||||
<summary className="text-primary hover:underline font-bold">查看常见 API 中转示例</summary>
|
||||
<div className="mt-2 p-3 bg-gray-100 border-2 border-black rounded-none space-y-1 text-xs shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p><strong>OpenAI 兼容格式:</strong></p>
|
||||
<p>• https://your-proxy.com/v1</p>
|
||||
<p>• https://api.openai-proxy.org/v1</p>
|
||||
<p className="pt-2"><strong>其他中转格式:</strong></p>
|
||||
<p>• https://your-api-gateway.com/openai</p>
|
||||
<p>• https://custom-endpoint.com/api</p>
|
||||
<p className="pt-2 text-orange-600">⚠️ 确保中转站支持你选择的 LLM 平台</p>
|
||||
<p className="pt-2 text-orange-600 font-bold">⚠️ 确保中转站支持你选择的 LLM 平台</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
|
@ -479,15 +481,16 @@ export function SystemConfig() {
|
|||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>超时时间(毫秒)</Label>
|
||||
<Label className="font-bold uppercase">超时时间(毫秒)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={config.llmTimeout}
|
||||
onChange={(e) => updateConfig('llmTimeout', Number(e.target.value))}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>温度参数(0-2)</Label>
|
||||
<Label className="font-bold uppercase">温度参数(0-2)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.1"
|
||||
|
|
@ -495,278 +498,293 @@ export function SystemConfig() {
|
|||
max="2"
|
||||
value={config.llmTemperature}
|
||||
onChange={(e) => updateConfig('llmTemperature', Number(e.target.value))}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>最大 Tokens</Label>
|
||||
<Label className="font-bold uppercase">最大 Tokens</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={config.llmMaxTokens}
|
||||
onChange={(e) => updateConfig('llmMaxTokens', Number(e.target.value))}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pt-4 border-t">
|
||||
<Label>自定义请求头(高级,可选)</Label>
|
||||
<div className="space-y-2 pt-4 border-t-2 border-black border-dashed">
|
||||
<Label className="font-bold uppercase">自定义请求头(高级,可选)</Label>
|
||||
<Input
|
||||
value={config.llmCustomHeaders}
|
||||
onChange={(e) => updateConfig('llmCustomHeaders', e.target.value)}
|
||||
placeholder='{"X-Custom-Header": "value", "Another-Header": "value2"}'
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
JSON 格式,用于某些中转站或自建服务的特殊要求。例如:<code className="bg-muted px-1 py-0.5 rounded">{"X-API-Version": "v1"}</code>
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
JSON 格式,用于某些中转站或自建服务的特殊要求。例如:<code className="bg-gray-200 px-1 py-0.5 border border-black">{"X-API-Version": "v1"}</code>
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 平台专用密钥 */}
|
||||
<TabsContent value="platforms" className="space-y-6">
|
||||
<Alert>
|
||||
<Key className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<div className="space-y-1">
|
||||
<p>配置各平台的 API Key,方便快速切换。如果设置了通用 API Key,将优先使用通用配置。</p>
|
||||
<p className="text-xs text-muted-foreground pt-1">
|
||||
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||
<Key className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div>
|
||||
<div className="space-y-1 font-mono text-sm text-blue-800">
|
||||
<p className="font-bold uppercase">配置各平台的 API Key</p>
|
||||
<p>方便快速切换。如果设置了通用 API Key,将优先使用通用配置。</p>
|
||||
<p className="text-xs text-blue-700 pt-1 font-bold">
|
||||
💡 <strong>使用 API 中转站的用户注意:</strong>这里填入的应该是<strong>中转站提供的 API Key</strong>,而不是官方 Key。
|
||||
中转站地址请在「LLM 配置」标签页的「API 基础 URL」中填写。
|
||||
</p>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{[
|
||||
{ key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方:https://makersuite.google.com/app/apikey | 或使用中转站 Key' },
|
||||
{ key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方:https://platform.openai.com/api-keys | 或使用中转站 Key' },
|
||||
{ key: 'claudeApiKey', label: 'Claude API Key', icon: '🟣', hint: '官方:https://console.anthropic.com/ | 或使用中转站 Key' },
|
||||
{ key: 'qwenApiKey', label: '通义千问 API Key', icon: '🟠', hint: '官方:https://dashscope.console.aliyun.com/ | 或使用中转站 Key' },
|
||||
{ key: 'deepseekApiKey', label: 'DeepSeek API Key', icon: '🔷', hint: '官方:https://platform.deepseek.com/ | 或使用中转站 Key' },
|
||||
{ key: 'zhipuApiKey', label: '智谱AI API Key', icon: '🔴', hint: '官方:https://open.bigmodel.cn/ | 或使用中转站 Key' },
|
||||
{ key: 'moonshotApiKey', label: 'Moonshot API Key', icon: '🌙', hint: '官方:https://platform.moonshot.cn/ | 或使用中转站 Key' },
|
||||
{ key: 'baiduApiKey', label: '百度文心 API Key', icon: '🔵', hint: '官方格式:API_KEY:SECRET_KEY | 或使用中转站 Key' },
|
||||
{ key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方:https://www.minimaxi.com/ | 或使用中转站 Key' },
|
||||
{ key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方:https://console.volcengine.com/ark | 或使用中转站 Key' },
|
||||
].map(({ key, label, icon, hint }) => (
|
||||
<Card key={key}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<span>{icon}</span>
|
||||
{label}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">{hint}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
type={showApiKeys[key] ? 'text' : 'password'}
|
||||
value={config[key as keyof SystemConfigData] as string}
|
||||
onChange={(e) => updateConfig(key as keyof SystemConfigData, e.target.value)}
|
||||
placeholder={`输入 ${label}`}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => toggleShowApiKey(key)}
|
||||
>
|
||||
{showApiKeys[key] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</Button>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{[
|
||||
{ key: 'geminiApiKey', label: 'Google Gemini API Key', icon: '🔵', hint: '官方:https://makersuite.google.com/app/apikey | 或使用中转站 Key' },
|
||||
{ key: 'openaiApiKey', label: 'OpenAI API Key', icon: '🟢', hint: '官方:https://platform.openai.com/api-keys | 或使用中转站 Key' },
|
||||
{ key: 'claudeApiKey', label: 'Claude API Key', icon: '🟣', hint: '官方:https://console.anthropic.com/ | 或使用中转站 Key' },
|
||||
{ key: 'qwenApiKey', label: '通义千问 API Key', icon: '🟠', hint: '官方:https://dashscope.console.aliyun.com/ | 或使用中转站 Key' },
|
||||
{ key: 'deepseekApiKey', label: 'DeepSeek API Key', icon: '🔷', hint: '官方:https://platform.deepseek.com/ | 或使用中转站 Key' },
|
||||
{ key: 'zhipuApiKey', label: '智谱AI API Key', icon: '🔴', hint: '官方:https://open.bigmodel.cn/ | 或使用中转站 Key' },
|
||||
{ key: 'moonshotApiKey', label: 'Moonshot API Key', icon: '🌙', hint: '官方:https://platform.moonshot.cn/ | 或使用中转站 Key' },
|
||||
{ key: 'baiduApiKey', label: '百度文心 API Key', icon: '🔵', hint: '官方格式:API_KEY:SECRET_KEY | 或使用中转站 Key' },
|
||||
{ key: 'minimaxApiKey', label: 'MiniMax API Key', icon: '⚡', hint: '官方:https://www.minimaxi.com/ | 或使用中转站 Key' },
|
||||
{ key: 'doubaoApiKey', label: '字节豆包 API Key', icon: '🎯', hint: '官方:https://console.volcengine.com/ark | 或使用中转站 Key' },
|
||||
].map(({ key, label, icon, hint }) => (
|
||||
<div key={key} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-sm font-display font-bold uppercase flex items-center gap-2">
|
||||
<span>{icon}</span>
|
||||
{label}
|
||||
</h3>
|
||||
<p className="text-[10px] text-gray-500 font-mono mt-1 truncate" title={hint}>{hint}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
<div className="p-4">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
type={showApiKeys[key] ? 'text' : 'password'}
|
||||
value={config[key as keyof SystemConfigData] as string}
|
||||
onChange={(e) => updateConfig(key as keyof SystemConfigData, e.target.value)}
|
||||
placeholder={`输入 ${label}`}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => toggleShowApiKey(key)}
|
||||
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10 flex-shrink-0"
|
||||
>
|
||||
{showApiKeys[key] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||
<span>🖥️</span>
|
||||
Ollama 基础 URL
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">本地 Ollama 服务的 API 端点</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">本地 Ollama 服务的 API 端点</p>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<Input
|
||||
value={config.ollamaBaseUrl}
|
||||
onChange={(e) => updateConfig('ollamaBaseUrl', e.target.value)}
|
||||
placeholder="http://localhost:11434/v1"
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 分析参数配置 */}
|
||||
<TabsContent value="analysis" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>代码分析参数</CardTitle>
|
||||
<CardDescription>调整代码分析的行为和性能</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">代码分析参数</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">调整代码分析的行为和性能</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="space-y-2">
|
||||
<Label>最大分析文件数</Label>
|
||||
<Label className="font-bold uppercase">最大分析文件数</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={config.maxAnalyzeFiles}
|
||||
onChange={(e) => updateConfig('maxAnalyzeFiles', Number(e.target.value))}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
单次分析任务最多处理的文件数量
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>LLM 并发请求数</Label>
|
||||
<Label className="font-bold uppercase">LLM 并发请求数</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={config.llmConcurrency}
|
||||
onChange={(e) => updateConfig('llmConcurrency', Number(e.target.value))}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
同时发送给 LLM 的请求数量(降低可避免速率限制)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>请求间隔(毫秒)</Label>
|
||||
<Label className="font-bold uppercase">请求间隔(毫秒)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={config.llmGapMs}
|
||||
onChange={(e) => updateConfig('llmGapMs', Number(e.target.value))}
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
每个 LLM 请求之间的延迟时间
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>输出语言</Label>
|
||||
<Label className="font-bold uppercase">输出语言</Label>
|
||||
<Select
|
||||
value={config.outputLanguage}
|
||||
onValueChange={(value) => updateConfig('outputLanguage', value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="retro-input h-10 bg-gray-50 border-2 border-black text-black focus:ring-0 focus:border-primary rounded-none font-mono">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="zh-CN">🇨🇳 中文</SelectItem>
|
||||
<SelectItem value="en-US">🇺🇸 English</SelectItem>
|
||||
<SelectContent className="retro-card border-2 border-black rounded-none">
|
||||
<SelectItem value="zh-CN" className="font-mono focus:bg-primary/20 focus:text-black">🇨🇳 中文</SelectItem>
|
||||
<SelectItem value="en-US" className="font-mono focus:bg-primary/20 focus:text-black">🇺🇸 English</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 其他配置 */}
|
||||
<TabsContent value="other" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>GitHub 集成</CardTitle>
|
||||
<CardDescription>配置 GitHub Personal Access Token 以访问私有仓库</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">GitHub 集成</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">配置 GitHub Personal Access Token 以访问私有仓库</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="space-y-2">
|
||||
<Label>GitHub Token(可选)</Label>
|
||||
<Label className="font-bold uppercase">GitHub Token(可选)</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
type={showApiKeys['github'] ? 'text' : 'password'}
|
||||
value={config.githubToken}
|
||||
onChange={(e) => updateConfig('githubToken', e.target.value)}
|
||||
placeholder="ghp_xxxxxxxxxxxx"
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => toggleShowApiKey('github')}
|
||||
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
|
||||
>
|
||||
{showApiKeys['github'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
获取:https://github.com/settings/tokens
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>GitLab 集成</CardTitle>
|
||||
<CardDescription>配置 GitLab Personal Access Token 以访问私有仓库</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">GitLab 集成</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">配置 GitLab Personal Access Token 以访问私有仓库</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="space-y-2">
|
||||
<Label>GitLab Token(可选)</Label>
|
||||
<Label className="font-bold uppercase">GitLab Token(可选)</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
type={showApiKeys['gitlab'] ? 'text' : 'password'}
|
||||
value={config.gitlabToken}
|
||||
onChange={(e) => updateConfig('gitlabToken', e.target.value)}
|
||||
placeholder="glpat-xxxxxxxxxxxx"
|
||||
className="retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => toggleShowApiKey('gitlab')}
|
||||
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 w-10"
|
||||
>
|
||||
{showApiKeys['gitlab'] ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-gray-500 font-bold">
|
||||
获取:https://gitlab.com/-/profile/personal_access_tokens
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>配置说明</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">配置说明</h3>
|
||||
</div>
|
||||
<div className="p-6 space-y-3 text-sm text-gray-600 font-mono font-medium">
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Database className="h-5 w-5 text-primary mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-foreground">用户配置</p>
|
||||
<p>
|
||||
<p className="font-bold text-black uppercase">用户配置</p>
|
||||
<p className="text-xs mt-1">
|
||||
配置保存在后端数据库中,与用户账号绑定。
|
||||
可以在不重新构建 Docker 镜像的情况下修改配置,配置会在所有分析任务中生效。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Settings className="h-5 w-5 text-green-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-foreground">配置优先级</p>
|
||||
<p>
|
||||
<p className="font-bold text-black uppercase">配置优先级</p>
|
||||
<p className="text-xs mt-1">
|
||||
运行时配置 > 构建时配置。如果设置了运行时配置,将覆盖构建时的环境变量。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Key className="h-5 w-5 text-orange-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-foreground">安全提示</p>
|
||||
<p>
|
||||
<p className="font-bold text-black uppercase">安全提示</p>
|
||||
<p className="text-xs mt-1">
|
||||
API Keys 存储在浏览器本地,其他网站无法访问。但清除浏览器数据会删除所有配置。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* 底部操作按钮 */}
|
||||
{hasChanges && (
|
||||
<div className="fixed bottom-6 right-6 flex gap-3 bg-background border rounded-lg shadow-lg p-4">
|
||||
<Button onClick={saveConfig} size="lg">
|
||||
<div className="fixed bottom-6 right-6 flex gap-3 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 z-50">
|
||||
<Button onClick={saveConfig} size="lg" className="retro-btn bg-black text-white border-2 border-black hover:bg-gray-800 rounded-none h-12 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]">
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
保存所有更改
|
||||
</Button>
|
||||
<Button onClick={loadConfig} variant="outline" size="lg">
|
||||
<Button onClick={loadConfig} variant="outline" size="lg" className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-12 font-bold uppercase">
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import {
|
||||
HardDrive,
|
||||
|
|
@ -97,10 +95,10 @@ export default function AdminDashboard() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||
<div className="space-y-4 text-center">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-primary mx-auto"></div>
|
||||
<p className="text-muted-foreground">加载数据库信息...</p>
|
||||
<div className="animate-spin rounded-none h-16 w-16 border-8 border-black border-t-transparent mx-auto"></div>
|
||||
<p className="text-black font-mono font-bold uppercase">加载数据库信息...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -109,17 +107,17 @@ export default function AdminDashboard() {
|
|||
return (
|
||||
<div className="space-y-6 pb-8">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||
<Settings className="h-8 w-8 text-primary" />
|
||||
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter flex items-center gap-3">
|
||||
<Settings className="h-8 w-8 text-black" />
|
||||
系统管理
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
<p className="text-gray-600 mt-2 font-mono border-l-2 border-primary pl-2">
|
||||
管理系统配置、LLM设置、数据库和存储使用情况
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={loadStats}>
|
||||
<Button variant="outline" onClick={loadStats} className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]">
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
刷新数据
|
||||
</Button>
|
||||
|
|
@ -127,12 +125,12 @@ export default function AdminDashboard() {
|
|||
|
||||
{/* 主要内容标签页 */}
|
||||
<Tabs defaultValue="config" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="config">系统配置</TabsTrigger>
|
||||
<TabsTrigger value="overview">数据概览</TabsTrigger>
|
||||
<TabsTrigger value="storage">存储管理</TabsTrigger>
|
||||
<TabsTrigger value="operations">数据操作</TabsTrigger>
|
||||
<TabsTrigger value="settings">高级设置</TabsTrigger>
|
||||
<TabsList className="grid w-full grid-cols-5 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6">
|
||||
<TabsTrigger value="config" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">系统配置</TabsTrigger>
|
||||
<TabsTrigger value="overview" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">数据概览</TabsTrigger>
|
||||
<TabsTrigger value="storage" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">存储管理</TabsTrigger>
|
||||
<TabsTrigger value="operations" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">数据操作</TabsTrigger>
|
||||
<TabsTrigger value="settings" className="rounded-none data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">高级设置</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 系统配置 */}
|
||||
|
|
@ -144,19 +142,19 @@ export default function AdminDashboard() {
|
|||
<TabsContent value="overview" className="space-y-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* 任务完成率 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
任务完成率
|
||||
</CardTitle>
|
||||
<CardDescription>审计任务的完成情况统计</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">审计任务的完成情况统计</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center justify-between text-sm font-bold">
|
||||
<span>已完成</span>
|
||||
<span className="font-medium">
|
||||
<span>
|
||||
{stats.totalTasks > 0
|
||||
? Math.round((stats.completedTasks / stats.totalTasks) * 100)
|
||||
: 0}%
|
||||
|
|
@ -167,35 +165,36 @@ export default function AdminDashboard() {
|
|||
? (stats.completedTasks / stats.totalTasks) * 100
|
||||
: 0
|
||||
}
|
||||
className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-green-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<div className="grid grid-cols-2 gap-4 pt-4 border-t-2 border-black border-dashed">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">总任务数</p>
|
||||
<p className="text-xs text-gray-500 uppercase font-bold">总任务数</p>
|
||||
<p className="text-2xl font-bold">{stats.totalTasks}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">已完成</p>
|
||||
<p className="text-xs text-gray-500 uppercase font-bold">已完成</p>
|
||||
<p className="text-2xl font-bold text-green-600">{stats.completedTasks}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 问题解决率 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||
<CheckCircle2 className="h-5 w-5" />
|
||||
问题解决率
|
||||
</CardTitle>
|
||||
<CardDescription>代码问题的解决情况统计</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">代码问题的解决情况统计</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center justify-between text-sm font-bold">
|
||||
<span>已解决</span>
|
||||
<span className="font-medium">
|
||||
<span>
|
||||
{stats.totalIssues > 0
|
||||
? Math.round((stats.resolvedIssues / stats.totalIssues) * 100)
|
||||
: 0}%
|
||||
|
|
@ -206,104 +205,104 @@ export default function AdminDashboard() {
|
|||
? (stats.resolvedIssues / stats.totalIssues) * 100
|
||||
: 0
|
||||
}
|
||||
className="bg-orange-100"
|
||||
className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-orange-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<div className="grid grid-cols-2 gap-4 pt-4 border-t-2 border-black border-dashed">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">总问题数</p>
|
||||
<p className="text-xs text-gray-500 uppercase font-bold">总问题数</p>
|
||||
<p className="text-2xl font-bold">{stats.totalIssues}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">已解决</p>
|
||||
<p className="text-xs text-gray-500 uppercase font-bold">已解决</p>
|
||||
<p className="text-2xl font-bold text-green-600">{stats.resolvedIssues}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据库表统计 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||
<Package className="h-5 w-5" />
|
||||
数据库表统计
|
||||
</CardTitle>
|
||||
<CardDescription>各数据表的记录数量</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">各数据表的记录数量</p>
|
||||
</div>
|
||||
<div className="p-6 font-mono">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="p-4 border-2 border-black bg-blue-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center gap-3">
|
||||
<FolderOpen className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">项目</p>
|
||||
<p className="text-xs text-gray-600 uppercase font-bold">项目</p>
|
||||
<p className="text-2xl font-bold">{stats.totalProjects}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="p-4 border-2 border-black bg-green-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center gap-3">
|
||||
<Clock className="h-8 w-8 text-green-600" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">审计任务</p>
|
||||
<p className="text-xs text-gray-600 uppercase font-bold">审计任务</p>
|
||||
<p className="text-2xl font-bold">{stats.totalTasks}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="p-4 border-2 border-black bg-orange-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="h-8 w-8 text-orange-600" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">问题</p>
|
||||
<p className="text-xs text-gray-600 uppercase font-bold">问题</p>
|
||||
<p className="text-2xl font-bold">{stats.totalIssues}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 存储管理 */}
|
||||
<TabsContent value="storage" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
|
||||
<HardDrive className="h-5 w-5" />
|
||||
存储空间使用情况
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">
|
||||
浏览器 IndexedDB 存储空间的使用详情
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-6 font-mono">
|
||||
{storageDetails ? (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center justify-between text-sm font-bold">
|
||||
<span>已使用空间</span>
|
||||
<span className="font-medium">{storageDetails.percentage}%</span>
|
||||
<span>{storageDetails.percentage}%</span>
|
||||
</div>
|
||||
<Progress value={storageDetails.percentage} />
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<Progress value={storageDetails.percentage} className="h-4 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||
<div className="flex items-center justify-between text-xs text-gray-500 font-bold">
|
||||
<span>{stats.storageUsed} 已使用</span>
|
||||
<span>{stats.storageQuota} 总配额</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4">
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<p className="text-sm text-muted-foreground mb-1">已使用</p>
|
||||
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p className="text-xs text-gray-600 uppercase font-bold mb-1">已使用</p>
|
||||
<p className="text-xl font-bold">{stats.storageUsed}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<p className="text-sm text-muted-foreground mb-1">总配额</p>
|
||||
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p className="text-xs text-gray-600 uppercase font-bold mb-1">总配额</p>
|
||||
<p className="text-xl font-bold">{stats.storageQuota}</p>
|
||||
</div>
|
||||
<div className="p-4 bg-muted rounded-lg">
|
||||
<p className="text-sm text-muted-foreground mb-1">剩余空间</p>
|
||||
<div className="p-4 bg-gray-100 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p className="text-xs text-gray-600 uppercase font-bold mb-1">剩余空间</p>
|
||||
<p className="text-xl font-bold">
|
||||
{((storageDetails.quota - storageDetails.usage) / 1024 / 1024).toFixed(2)} MB
|
||||
</p>
|
||||
|
|
@ -311,59 +310,65 @@ export default function AdminDashboard() {
|
|||
</div>
|
||||
|
||||
{storageDetails.percentage > 80 && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
存储空间使用率已超过 80%,建议清理不需要的数据或导出备份后清空数据库。
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div className="bg-red-50 border-2 border-red-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(239,68,68,1)]">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-bold text-red-800 uppercase">警告</p>
|
||||
<p className="text-sm text-red-700 font-medium">
|
||||
存储空间使用率已超过 80%,建议清理不需要的数据或导出备份后清空数据库。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
无法获取存储空间信息。您的浏览器可能不支持 Storage API。
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-bold text-blue-800 uppercase">提示</p>
|
||||
<p className="text-sm text-blue-700 font-medium">
|
||||
无法获取存储空间信息。您的浏览器可能不支持 Storage API。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>存储优化建议</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">存储优化建议</h3>
|
||||
</div>
|
||||
<div className="p-6 space-y-3 font-mono">
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium">定期导出备份</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-bold text-black uppercase text-sm">定期导出备份</p>
|
||||
<p className="text-xs text-gray-600 font-medium">
|
||||
建议定期导出数据为 JSON 文件,防止数据丢失
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium">清理旧数据</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-bold text-black uppercase text-sm">清理旧数据</p>
|
||||
<p className="text-xs text-gray-600 font-medium">
|
||||
删除不再需要的项目和任务可以释放存储空间
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3 p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium">监控存储使用</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-bold text-black uppercase text-sm">监控存储使用</p>
|
||||
<p className="text-xs text-gray-600 font-medium">
|
||||
定期检查存储使用情况,避免超出浏览器限制
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 数据操作 */}
|
||||
|
|
@ -373,63 +378,66 @@ export default function AdminDashboard() {
|
|||
|
||||
{/* 设置 */}
|
||||
<TabsContent value="settings" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>数据库设置</CardTitle>
|
||||
<CardDescription>配置数据库行为和性能选项</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<strong>当前数据库模式:</strong> {
|
||||
dbMode === 'api' ? '后端 PostgreSQL 数据库' :
|
||||
dbMode === 'local' ? '本地 IndexedDB' :
|
||||
dbMode === 'supabase' ? 'Supabase 云端(已废弃)' :
|
||||
'演示模式'
|
||||
}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">数据库设置</h3>
|
||||
<p className="text-xs text-gray-500 font-mono mt-1">配置数据库行为和性能选项</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[4px_4px_0px_0px_rgba(59,130,246,1)]">
|
||||
<Info className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-bold text-blue-800 uppercase text-sm">当前数据库模式</p>
|
||||
<p className="text-sm text-blue-700 font-medium mt-1">
|
||||
{
|
||||
dbMode === 'api' ? '后端 PostgreSQL 数据库' :
|
||||
dbMode === 'local' ? '本地 IndexedDB' :
|
||||
dbMode === 'supabase' ? 'Supabase 云端(已废弃)' :
|
||||
'演示模式'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 pt-4">
|
||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div>
|
||||
<p className="font-medium">自动备份</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-bold text-black uppercase text-sm">自动备份</p>
|
||||
<p className="text-xs text-gray-500 font-medium">
|
||||
定期自动导出数据备份(开发中)
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline">即将推出</Badge>
|
||||
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs">即将推出</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div>
|
||||
<p className="font-medium">数据压缩</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-bold text-black uppercase text-sm">数据压缩</p>
|
||||
<p className="text-xs text-gray-500 font-medium">
|
||||
压缩存储数据以节省空间(开发中)
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline">即将推出</Badge>
|
||||
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs">即将推出</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div className="flex items-center justify-between p-4 border-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div>
|
||||
<p className="font-medium">数据同步</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-bold text-black uppercase text-sm">数据同步</p>
|
||||
<p className="text-xs text-gray-500 font-medium">
|
||||
在多个设备间同步数据(开发中)
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline">即将推出</Badge>
|
||||
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs">即将推出</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>关于本地数据库</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase">关于本地数据库</h3>
|
||||
</div>
|
||||
<div className="p-6 space-y-3 text-sm text-gray-600 font-mono font-medium">
|
||||
<p>
|
||||
本地数据库使用浏览器的 IndexedDB 技术存储数据,具有以下特点:
|
||||
</p>
|
||||
|
|
@ -440,11 +448,11 @@ export default function AdminDashboard() {
|
|||
<li>清除浏览器数据会删除所有本地数据</li>
|
||||
<li>不同浏览器的数据相互独立</li>
|
||||
</ul>
|
||||
<p className="pt-2">
|
||||
<strong>建议:</strong>定期导出数据备份,以防意外数据丢失。
|
||||
<p className="pt-2 border-t-2 border-black border-dashed mt-4">
|
||||
<strong className="text-black uppercase">建议:</strong>定期导出数据备份,以防意外数据丢失。
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Activity,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
import {
|
||||
Activity,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Search,
|
||||
FileText,
|
||||
Calendar,
|
||||
|
|
@ -45,7 +45,7 @@ export default function AuditTasks() {
|
|||
try {
|
||||
// 只获取活动任务的最新数据
|
||||
const updatedData = await api.getAuditTasks();
|
||||
|
||||
|
||||
// 使用函数式更新,确保基于最新状态
|
||||
setTasks(prevTasks => {
|
||||
return prevTasks.map(prevTask => {
|
||||
|
|
@ -114,265 +114,254 @@ export default function AuditTasks() {
|
|||
|
||||
const filteredTasks = tasks.filter(task => {
|
||||
const matchesSearch = task.project?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
task.task_type.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
task.task_type.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
const matchesStatus = statusFilter === "all" || task.status === statusFilter;
|
||||
return matchesSearch && matchesStatus;
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div>
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||
<div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<div className="space-y-6 animate-fade-in font-mono">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||
<div>
|
||||
<h1 className="page-title">审计任务</h1>
|
||||
<p className="page-subtitle">查看和管理所有代码审计任务的执行状态</p>
|
||||
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">审计任务</h1>
|
||||
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">查看和管理所有代码审计任务的执行状态</p>
|
||||
</div>
|
||||
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-12 px-6 text-lg font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all" onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="w-5 h-5 mr-2" />
|
||||
新建任务
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">总任务数</p>
|
||||
<p className="stat-value text-xl">{tasks.length}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-primary to-accent">
|
||||
<Activity className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">总任务数</p>
|
||||
<p className="text-3xl font-bold text-black font-mono">{tasks.length}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-primary border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Activity className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">已完成</p>
|
||||
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'completed').length}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-emerald-500 to-emerald-600">
|
||||
<CheckCircle className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">已完成</p>
|
||||
<p className="text-3xl font-bold text-green-600 font-mono">{tasks.filter(t => t.status === 'completed').length}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-green-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<CheckCircle className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">运行中</p>
|
||||
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'running').length}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-orange-500 to-orange-600">
|
||||
<Clock className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">运行中</p>
|
||||
<p className="text-3xl font-bold text-orange-600 font-mono">{tasks.filter(t => t.status === 'running').length}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-orange-500 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Clock className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">失败</p>
|
||||
<p className="stat-value text-xl">{tasks.filter(t => t.status === 'failed').length}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-red-500 to-red-600">
|
||||
<AlertTriangle className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">失败</p>
|
||||
<p className="text-3xl font-bold text-red-600 font-mono">{tasks.filter(t => t.status === 'failed').length}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-red-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<AlertTriangle className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索和筛选 */}
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="搜索项目名称或任务类型..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant={statusFilter === "all" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("all")}
|
||||
>
|
||||
全部
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "running" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("running")}
|
||||
>
|
||||
运行中
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "completed" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("completed")}
|
||||
>
|
||||
已完成
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "failed" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("failed")}
|
||||
>
|
||||
失败
|
||||
</Button>
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-4">
|
||||
<div className="flex-1 relative w-full">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||
<Input
|
||||
placeholder="搜索项目名称或任务类型..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 retro-input h-10"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex space-x-2 w-full md:w-auto overflow-x-auto pb-2 md:pb-0">
|
||||
<Button
|
||||
variant={statusFilter === "all" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("all")}
|
||||
className={`retro-btn h-10 ${statusFilter === "all" ? "bg-black text-white" : "bg-white text-black hover:bg-gray-100"}`}
|
||||
>
|
||||
全部
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "running" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("running")}
|
||||
className={`retro-btn h-10 ${statusFilter === "running" ? "bg-orange-500 text-white" : "bg-white text-black hover:bg-orange-100"}`}
|
||||
>
|
||||
运行中
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "completed" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("completed")}
|
||||
className={`retro-btn h-10 ${statusFilter === "completed" ? "bg-green-600 text-white" : "bg-white text-black hover:bg-green-100"}`}
|
||||
>
|
||||
已完成
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "failed" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setStatusFilter("failed")}
|
||||
className={`retro-btn h-10 ${statusFilter === "failed" ? "bg-red-600 text-white" : "bg-white text-black hover:bg-red-100"}`}
|
||||
>
|
||||
失败
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 任务列表 */}
|
||||
{filteredTasks.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{filteredTasks.map((task) => (
|
||||
<Card key={task.id} className="card-modern group">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
|
||||
task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
|
||||
task.status === 'running' ? 'bg-red-50 text-red-600' :
|
||||
<div key={task.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between mb-6 border-b-2 border-dashed border-gray-300 pb-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className={`w-12 h-12 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${task.status === 'completed' ? 'bg-green-100 text-green-600' :
|
||||
task.status === 'running' ? 'bg-orange-100 text-orange-600' :
|
||||
task.status === 'failed' ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{getStatusIcon(task.status)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg text-gray-900 group-hover:text-primary transition-colors">
|
||||
{task.project?.name || '未知项目'}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
||||
</p>
|
||||
</div>
|
||||
{getStatusIcon(task.status)}
|
||||
</div>
|
||||
<Badge className={getStatusColor(task.status)}>
|
||||
{task.status === 'completed' ? '已完成' :
|
||||
task.status === 'running' ? '运行中' :
|
||||
task.status === 'failed' ? '失败' :
|
||||
task.status === 'cancelled' ? '已取消' : '等待中'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div className="text-center p-4 bg-gradient-to-br from-blue-50 to-blue-100/30 rounded-xl border border-blue-200">
|
||||
<div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
|
||||
<p className="text-xs text-blue-700 font-medium">文件数</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-gradient-to-br from-purple-50 to-purple-100/30 rounded-xl border border-purple-200">
|
||||
<div className="text-2xl font-bold text-purple-600 mb-1">{task.total_lines.toLocaleString()}</div>
|
||||
<p className="text-xs text-purple-700 font-medium">代码行数</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-gradient-to-br from-orange-50 to-orange-100/30 rounded-xl border border-orange-200">
|
||||
<div className="text-2xl font-bold text-orange-600 mb-1">{task.issues_count}</div>
|
||||
<p className="text-xs text-orange-700 font-medium">发现问题</p>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-gradient-to-br from-green-50 to-green-100/30 rounded-xl border border-green-200">
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">{task.quality_score.toFixed(1)}</div>
|
||||
<p className="text-xs text-green-700 font-medium">质量评分</p>
|
||||
<div>
|
||||
<h3 className="font-bold text-xl text-black font-display uppercase">
|
||||
{task.project?.name || '未知项目'}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 font-mono font-bold">
|
||||
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`rounded-none border-2 border-black font-bold uppercase ${getStatusColor(task.status)}`}>
|
||||
{task.status === 'completed' ? '已完成' :
|
||||
task.status === 'running' ? '运行中' :
|
||||
task.status === 'failed' ? '失败' :
|
||||
task.status === 'cancelled' ? '已取消' : '等待中'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* 扫描进度 */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-700">扫描进度</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{task.scanned_files || 0} / {task.total_files || 0} 文件
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-gradient-to-r from-primary to-accent h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${calculateTaskProgress(task.scanned_files, task.total_files)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-right mt-1">
|
||||
<span className="text-xs text-gray-500">
|
||||
{calculateTaskProgress(task.scanned_files, task.total_files)}% 完成
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6 font-mono">
|
||||
<div className="text-center p-3 bg-blue-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="text-2xl font-bold text-blue-600 mb-1">{task.total_files}</div>
|
||||
<p className="text-xs text-blue-800 font-bold uppercase">文件数</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-purple-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="text-2xl font-bold text-purple-600 mb-1">{task.total_lines.toLocaleString()}</div>
|
||||
<p className="text-xs text-purple-800 font-bold uppercase">代码行数</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-orange-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="text-2xl font-bold text-orange-600 mb-1">{task.issues_count}</div>
|
||||
<p className="text-xs text-orange-800 font-bold uppercase">发现问题</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-green-50 border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">{task.quality_score.toFixed(1)}</div>
|
||||
<p className="text-xs text-green-800 font-bold uppercase">质量评分</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
||||
<div className="flex items-center space-x-6 text-sm text-gray-500">
|
||||
{/* 扫描进度 */}
|
||||
<div className="mb-6 font-mono">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-bold text-black uppercase">扫描进度</span>
|
||||
<span className="text-sm font-bold text-gray-600">
|
||||
{task.scanned_files || 0} / {task.total_files || 0} 文件
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 h-4 border-2 border-black">
|
||||
<div
|
||||
className="bg-primary h-full transition-all duration-300 border-r-2 border-black"
|
||||
style={{ width: `${calculateTaskProgress(task.scanned_files, task.total_files)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-right mt-1">
|
||||
<span className="text-xs font-bold text-gray-600">
|
||||
{calculateTaskProgress(task.scanned_files, task.total_files)}% 完成
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 border-t-2 border-black bg-gray-50 -mx-6 -mb-6 p-6 mt-6">
|
||||
<div className="flex items-center space-x-6 text-sm text-gray-600 font-mono font-bold">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-2" />
|
||||
{formatDate(task.created_at)}
|
||||
</div>
|
||||
{task.completed_at && (
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-2" />
|
||||
{formatDate(task.created_at)}
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
{formatDate(task.completed_at)}
|
||||
</div>
|
||||
{task.completed_at && (
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
{formatDate(task.completed_at)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Link to={`/tasks/${task.id}`}>
|
||||
<Button variant="outline" size="sm" className="btn-secondary">
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
查看详情
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Link to={`/tasks/${task.id}`}>
|
||||
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-9">
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
查看详情
|
||||
</Button>
|
||||
</Link>
|
||||
{task.project && (
|
||||
<Link to={`/projects/${task.project.id}`}>
|
||||
<Button size="sm" className="retro-btn bg-black text-white hover:bg-gray-800 h-9">
|
||||
查看项目
|
||||
</Button>
|
||||
</Link>
|
||||
{task.project && (
|
||||
<Link to={`/projects/${task.project.id}`}>
|
||||
<Button size="sm" className="btn-primary">
|
||||
查看项目
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Card className="card-modern">
|
||||
<CardContent className="empty-state py-16">
|
||||
<div className="empty-icon">
|
||||
<Activity className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-6 max-w-md">
|
||||
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
|
||||
</p>
|
||||
{!searchTerm && statusFilter === "all" && (
|
||||
<Button className="btn-primary" onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
创建任务
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-16 flex flex-col items-center justify-center text-center">
|
||||
<div className="w-20 h-20 bg-gray-100 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Activity className="w-10 h-10 text-gray-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
|
||||
{searchTerm || statusFilter !== "all" ? '未找到匹配的任务' : '暂无审计任务'}
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-8 max-w-md font-mono">
|
||||
{searchTerm || statusFilter !== "all" ? '尝试调整搜索条件或筛选器' : '创建第一个审计任务开始代码质量分析'}
|
||||
</p>
|
||||
{!searchTerm && statusFilter === "all" && (
|
||||
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-12 px-8 text-lg font-bold uppercase" onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="w-5 h-5 mr-2" />
|
||||
创建任务
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 新建任务对话框 */}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
|
|
@ -11,13 +11,13 @@ import {
|
|||
import {
|
||||
Activity, AlertTriangle, Clock, Code,
|
||||
FileText, GitBranch, Shield, TrendingUp, Zap,
|
||||
BarChart3, Target, ArrowUpRight, Calendar
|
||||
BarChart3, Target, ArrowUpRight, Calendar, Terminal
|
||||
} from "lucide-react";
|
||||
import { api, dbMode, isDemoMode } from "@/shared/config/database";
|
||||
import type { Project, AuditTask, ProjectStats } from "@/shared/types";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
||||
import { Info } from "lucide-react";
|
||||
|
||||
export default function Dashboard() {
|
||||
|
|
@ -85,7 +85,7 @@ export default function Dashboard() {
|
|||
.sort((a, b) => new Date(a.completed_at!).getTime() - new Date(b.completed_at!).getTime())
|
||||
.slice(-6); // 最近6个任务
|
||||
|
||||
const trendData = tasksByDate.map((task, index) => ({
|
||||
const trendData = tasksByDate.map((task) => ({
|
||||
date: new Date(task.completed_at!).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }),
|
||||
score: task.quality_score
|
||||
}));
|
||||
|
|
@ -152,38 +152,44 @@ export default function Dashboard() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<div className="flex items-center justify-center min-h-[60vh] font-mono">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="relative w-16 h-16 mx-auto">
|
||||
<div className="absolute inset-0 border-4 border-blue-200 rounded-full"></div>
|
||||
<div className="absolute inset-0 border-4 border-blue-600 rounded-full border-t-transparent animate-spin"></div>
|
||||
<div className="absolute inset-0 border-4 border-gray-200 rounded-none"></div>
|
||||
<div className="absolute inset-0 border-4 border-primary rounded-none border-t-transparent animate-spin"></div>
|
||||
</div>
|
||||
<p className="text-gray-600">加载仪表盘数据...</p>
|
||||
<p className="text-gray-600 uppercase font-bold">加载仪表盘数据...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 animate-fade-in">
|
||||
{/* Simplified Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div className="space-y-6 p-6 bg-background min-h-screen font-mono relative overflow-hidden">
|
||||
{/* Decorative Background */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||
|
||||
{/* Header Section */}
|
||||
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||
<div>
|
||||
<h1 className="page-title">仪表盘</h1>
|
||||
<p className="page-subtitle">
|
||||
实时监控项目状态,掌握代码质量动态
|
||||
{isDemoMode && <Badge variant="outline" className="ml-2">演示模式</Badge>}
|
||||
<h1 className="text-4xl font-display font-bold uppercase tracking-tighter mb-2">
|
||||
系统<span className="text-primary">_仪表盘</span>
|
||||
</h1>
|
||||
<p className="text-gray-500 font-mono text-sm flex items-center gap-2">
|
||||
<Terminal className="w-4 h-4" />
|
||||
// 监控状态 // 代码质量概览
|
||||
{isDemoMode && <Badge variant="outline" className="ml-2 border-black bg-yellow-100 text-yellow-800 rounded-none">演示模式</Badge>}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Link to="/instant-analysis">
|
||||
<Button className="btn-primary">
|
||||
<Button className="retro-btn h-12">
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
即时分析
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/projects">
|
||||
<Button variant="outline" className="btn-secondary">
|
||||
<Button variant="outline" className="retro-btn bg-white text-black hover:bg-gray-100 h-12">
|
||||
<GitBranch className="w-4 h-4 mr-2" />
|
||||
新建项目
|
||||
</Button>
|
||||
|
|
@ -193,88 +199,80 @@ export default function Dashboard() {
|
|||
|
||||
{/* 数据库模式提示 */}
|
||||
{isDemoMode && (
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<div className="relative z-10 bg-yellow-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] flex items-start gap-3">
|
||||
<Info className="h-5 w-5 text-black mt-0.5" />
|
||||
<div className="text-sm font-mono text-black">
|
||||
当前使用<strong>演示模式</strong>,显示的是模拟数据。
|
||||
配置数据库后将显示真实数据。
|
||||
<Link to="/admin" className="ml-2 text-primary hover:underline">
|
||||
前往数据库管理 →
|
||||
<Link to="/admin" className="ml-2 text-primary font-bold hover:underline uppercase">
|
||||
前往数据库管理 >
|
||||
</Link>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<Card className="stat-card group">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">总项目数</p>
|
||||
<p className="stat-value">{stats?.total_projects || 0}</p>
|
||||
<p className="text-xs text-gray-500 mt-1">活跃 {stats?.active_projects || 0} 个</p>
|
||||
</div>
|
||||
<div className="stat-icon from-primary to-accent group-hover:scale-110 transition-transform">
|
||||
<Code className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 relative z-10">
|
||||
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-mono text-xs font-bold uppercase text-gray-500">总项目数</p>
|
||||
<p className="font-display text-3xl font-bold">{stats?.total_projects || 0}</p>
|
||||
<p className="text-xs font-mono mt-1 border-l-2 border-primary pl-2">活跃: {stats?.active_projects || 0}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-12 h-12 border-2 border-black bg-primary flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Code className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card group">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">审计任务</p>
|
||||
<p className="stat-value">{stats?.total_tasks || 0}</p>
|
||||
<p className="text-xs text-gray-500 mt-1">已完成 {stats?.completed_tasks || 0} 个</p>
|
||||
</div>
|
||||
<div className="stat-icon from-emerald-500 to-emerald-600 group-hover:scale-110 transition-transform">
|
||||
<Activity className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-mono text-xs font-bold uppercase text-gray-500">审计任务</p>
|
||||
<p className="font-display text-3xl font-bold">{stats?.total_tasks || 0}</p>
|
||||
<p className="text-xs font-mono mt-1 border-l-2 border-green-500 pl-2">已完成: {stats?.completed_tasks || 0}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-12 h-12 border-2 border-black bg-green-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Activity className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card group">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">发现问题</p>
|
||||
<p className="stat-value">{stats?.total_issues || 0}</p>
|
||||
<p className="text-xs text-gray-500 mt-1">已解决 {stats?.resolved_issues || 0} 个</p>
|
||||
</div>
|
||||
<div className="stat-icon from-orange-500 to-orange-600 group-hover:scale-110 transition-transform">
|
||||
<AlertTriangle className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-mono text-xs font-bold uppercase text-gray-500">发现问题</p>
|
||||
<p className="font-display text-3xl font-bold">{stats?.total_issues || 0}</p>
|
||||
<p className="text-xs font-mono mt-1 border-l-2 border-orange-500 pl-2">已解决: {stats?.resolved_issues || 0}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-12 h-12 border-2 border-black bg-orange-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card group">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">平均质量分</p>
|
||||
<p className="stat-value">
|
||||
{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}
|
||||
</p>
|
||||
{stats?.avg_quality_score ? (
|
||||
<div className="flex items-center text-xs text-emerald-600 font-medium mt-1">
|
||||
<TrendingUp className="w-3 h-3 mr-1" />
|
||||
<span>持续改进中</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-gray-500 mt-1">暂无数据</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="stat-icon from-purple-500 to-purple-600 group-hover:scale-110 transition-transform">
|
||||
<Target className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div className="retro-card p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-mono text-xs font-bold uppercase text-gray-500">平均质量分</p>
|
||||
<p className="font-display text-3xl font-bold">
|
||||
{stats?.avg_quality_score ? stats.avg_quality_score.toFixed(1) : '0.0'}
|
||||
</p>
|
||||
{stats?.avg_quality_score ? (
|
||||
<div className="flex items-center text-xs font-mono font-bold text-emerald-600 mt-1">
|
||||
<TrendingUp className="w-3 h-3 mr-1" />
|
||||
<span>持续改进</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs font-mono text-gray-500 mt-1">暂无数据</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-12 h-12 border-2 border-black bg-purple-500 flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Target className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content - 重新设计为更紧凑的布局 */}
|
||||
|
|
@ -282,60 +280,62 @@ export default function Dashboard() {
|
|||
{/* 左侧主要内容区 */}
|
||||
<div className="xl:col-span-3 space-y-4">
|
||||
{/* 图表区域 - 使用更紧凑的网格布局 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* 图表区域 - 使用更紧凑的网格布局 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* 质量趋势图 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center text-lg">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<TrendingUp className="w-5 h-5 mr-2 text-primary" />
|
||||
代码质量趋势
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
{qualityTrendData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<LineChart data={qualityTrendData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
||||
<XAxis dataKey="date" stroke="#6b7280" fontSize={12} />
|
||||
<YAxis stroke="#6b7280" fontSize={12} domain={[0, 100]} />
|
||||
<XAxis dataKey="date" stroke="#000" fontSize={12} tick={{ fontFamily: 'Space Mono' }} />
|
||||
<YAxis stroke="#000" fontSize={12} domain={[0, 100]} tick={{ fontFamily: 'Space Mono' }} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)'
|
||||
backgroundColor: '#fff',
|
||||
border: '2px solid #000',
|
||||
borderRadius: '0px',
|
||||
boxShadow: '4px 4px 0px 0px rgba(0,0,0,1)',
|
||||
fontFamily: 'Space Mono'
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
type="step"
|
||||
dataKey="score"
|
||||
stroke="hsl(var(--primary))"
|
||||
stroke="#000"
|
||||
strokeWidth={3}
|
||||
dot={{ fill: 'hsl(var(--primary))', strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6 }}
|
||||
dot={{ fill: '#fff', stroke: '#000', strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6, fill: '#000' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[250px] text-gray-400">
|
||||
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300">
|
||||
<div className="text-center">
|
||||
<TrendingUp className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">暂无质量趋势数据</p>
|
||||
<p className="text-sm font-mono uppercase">暂无质量趋势数据</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 问题分布图 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center text-lg">
|
||||
<BarChart3 className="w-5 h-5 mr-2 text-accent" />
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<BarChart3 className="w-5 h-5 mr-2 text-black" />
|
||||
问题类型分布
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
{issueTypeData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<PieChart>
|
||||
|
|
@ -343,106 +343,116 @@ export default function Dashboard() {
|
|||
data={issueTypeData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
labelLine={true}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
stroke="#000"
|
||||
strokeWidth={2}
|
||||
>
|
||||
{issueTypeData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
{issueTypeData.map((entry) => (
|
||||
<Cell key={`cell-${entry.name}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#fff',
|
||||
border: '2px solid #000',
|
||||
borderRadius: '0px',
|
||||
boxShadow: '4px 4px 0px 0px rgba(0,0,0,1)',
|
||||
fontFamily: 'Space Mono'
|
||||
}}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[250px] text-gray-400">
|
||||
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300">
|
||||
<div className="text-center">
|
||||
<BarChart3 className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">暂无问题分布数据</p>
|
||||
<p className="text-sm font-mono uppercase">暂无问题分布数据</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 项目概览 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center text-lg">
|
||||
{/* 项目概览 */}
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<FileText className="w-5 h-5 mr-2 text-primary" />
|
||||
项目概览
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</h3>
|
||||
</div>
|
||||
<div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{recentProjects.length > 0 ? (
|
||||
recentProjects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
to={`/projects/${project.id}`}
|
||||
className="block p-4 rounded-lg border border-gray-200 hover:border-primary/30 hover:bg-primary/5 transition-all group"
|
||||
className="block p-4 border-2 border-black bg-gray-50 hover:bg-white hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] transition-all group"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h4 className="font-medium text-gray-900 group-hover:text-primary transition-colors truncate">
|
||||
<h4 className="font-mono font-bold text-black group-hover:text-primary transition-colors truncate uppercase">
|
||||
{project.name}
|
||||
</h4>
|
||||
<Badge
|
||||
variant={project.is_active ? "default" : "secondary"}
|
||||
className="ml-2 flex-shrink-0"
|
||||
variant="outline"
|
||||
className={`ml-2 flex-shrink-0 border-black rounded-none ${project.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
|
||||
>
|
||||
{project.is_active ? '活跃' : '暂停'}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 line-clamp-2 mb-2">
|
||||
<p className="text-xs text-gray-600 font-mono line-clamp-2 mb-3 border-l-2 border-gray-300 pl-2">
|
||||
{project.description || '暂无描述'}
|
||||
</p>
|
||||
<div className="flex items-center text-xs text-gray-400">
|
||||
<div className="flex items-center text-xs text-gray-500 font-mono">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{new Date(project.created_at).toLocaleDateString('zh-CN')}
|
||||
</div>
|
||||
</Link>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-full text-center py-8 text-gray-500">
|
||||
<div className="col-span-full text-center py-8 text-gray-500 border-2 border-dashed border-black">
|
||||
<Code className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">暂无项目</p>
|
||||
<p className="text-sm font-mono uppercase">暂无项目</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 最近任务 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center text-lg">
|
||||
<Clock className="w-5 h-5 mr-2 text-emerald-600" />
|
||||
最近任务
|
||||
</CardTitle>
|
||||
<Link to="/audit-tasks">
|
||||
<Button variant="ghost" size="sm" className="hover:bg-emerald-50 hover:text-emerald-700">
|
||||
查看全部
|
||||
<ArrowUpRight className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* 最近任务 */}
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4 flex items-center justify-between">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<Clock className="w-5 h-5 mr-2 text-green-600" />
|
||||
最近任务
|
||||
</h3>
|
||||
<Link to="/audit-tasks">
|
||||
<Button variant="ghost" size="sm" className="hover:bg-green-50 hover:text-green-700 font-mono uppercase text-xs">
|
||||
查看全部
|
||||
<ArrowUpRight className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-3">
|
||||
{recentTasks.length > 0 ? (
|
||||
recentTasks.slice(0, 6).map((task) => (
|
||||
<Link
|
||||
key={task.id}
|
||||
to={`/tasks/${task.id}`}
|
||||
className="flex items-center justify-between p-3 rounded-lg hover:bg-gray-50 transition-colors group"
|
||||
className="flex items-center justify-between p-3 border-2 border-transparent hover:border-black hover:bg-gray-50 transition-all group"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
|
||||
task.status === 'running' ? 'bg-red-50 text-red-600' :
|
||||
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${task.status === 'completed' ? 'bg-green-100 text-green-600' :
|
||||
task.status === 'running' ? 'bg-blue-100 text-blue-600' :
|
||||
'bg-red-100 text-red-600'
|
||||
}`}>
|
||||
{task.status === 'completed' ? <Activity className="w-4 h-4" /> :
|
||||
|
|
@ -450,113 +460,115 @@ export default function Dashboard() {
|
|||
<AlertTriangle className="w-4 h-4" />}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-sm text-gray-900 group-hover:text-primary transition-colors">
|
||||
<p className="font-mono font-bold text-sm text-black group-hover:text-primary transition-colors uppercase">
|
||||
{task.project?.name || '未知项目'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
质量分: {task.quality_score?.toFixed(1) || '0.0'}
|
||||
<p className="text-xs text-gray-500 font-mono">
|
||||
质量分: <span className="font-bold text-black">{task.quality_score?.toFixed(1) || '0.0'}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={getStatusColor(task.status)}>
|
||||
<Badge className={`rounded-none border-black border ${getStatusColor(task.status)}`}>
|
||||
{task.status === 'completed' ? '完成' :
|
||||
task.status === 'running' ? '运行中' : '失败'}
|
||||
</Badge>
|
||||
</Link>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<div className="text-center py-8 text-gray-500 border-2 border-dashed border-black">
|
||||
<Activity className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">暂无任务</p>
|
||||
<p className="text-sm font-mono uppercase">暂无任务</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧边栏 - 紧凑设计 */}
|
||||
<div className="xl:col-span-1 space-y-4">
|
||||
{/* 快速操作 */}
|
||||
<Card className="card-modern bg-gradient-to-br from-red-50/30 via-background to-red-50/20 border border-red-100/50">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg flex items-center">
|
||||
<Zap className="w-5 h-5 mr-2 text-indigo-600" />
|
||||
{/* 快速操作 */}
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<Zap className="w-5 h-5 mr-2 text-primary" />
|
||||
快速操作
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Link to="/instant-analysis" className="block">
|
||||
<Button className="w-full justify-start btn-primary">
|
||||
<Button className="w-full justify-start retro-btn h-10">
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
即时代码分析
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/projects" className="block">
|
||||
<Button variant="outline" className="w-full justify-start btn-secondary">
|
||||
<Button variant="outline" className="w-full justify-start retro-btn bg-white text-black hover:bg-gray-100 h-10">
|
||||
<GitBranch className="w-4 h-4 mr-2" />
|
||||
创建新项目
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/audit-tasks" className="block">
|
||||
<Button variant="outline" className="w-full justify-start btn-secondary">
|
||||
<Button variant="outline" className="w-full justify-start retro-btn bg-white text-black hover:bg-gray-100 h-10">
|
||||
<Shield className="w-4 h-4 mr-2" />
|
||||
启动审计任务
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 系统状态 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg flex items-center">
|
||||
<Activity className="w-5 h-5 mr-2 text-emerald-600" />
|
||||
{/* 系统状态 */}
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<Activity className="w-5 h-5 mr-2 text-green-600" />
|
||||
系统状态
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-4 font-mono">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">数据库模式</span>
|
||||
<Badge className={
|
||||
dbMode === 'api' ? 'bg-purple-100 text-purple-700' :
|
||||
dbMode === 'local' ? 'bg-blue-100 text-blue-700' :
|
||||
dbMode === 'supabase' ? 'bg-green-100 text-green-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}>
|
||||
<span className="text-sm text-gray-600 uppercase">数据库模式</span>
|
||||
<Badge className={`rounded-none border border-black ${dbMode === 'api' ? 'bg-purple-100 text-purple-700' :
|
||||
dbMode === 'local' ? 'bg-blue-100 text-blue-700' :
|
||||
dbMode === 'supabase' ? 'bg-green-100 text-green-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
{dbMode === 'api' ? '后端' : dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">活跃项目</span>
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
<span className="text-sm text-gray-600 uppercase">活跃项目</span>
|
||||
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-yellow-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
{stats?.active_projects || 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">运行中任务</span>
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
<span className="text-sm text-gray-600 uppercase">运行中任务</span>
|
||||
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-blue-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
{recentTasks.filter(t => t.status === 'running').length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">待解决问题</span>
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
<span className="text-sm text-gray-600 uppercase">待解决问题</span>
|
||||
<span className="text-sm font-bold text-black border-2 border-black px-2 bg-red-100 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
{stats ? stats.total_issues - stats.resolved_issues : 0}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 最新活动 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg flex items-center">
|
||||
{/* 最新活动 */}
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<Clock className="w-5 h-5 mr-2 text-orange-600" />
|
||||
最新活动
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{recentTasks.length > 0 ? (
|
||||
recentTasks.slice(0, 3).map((task) => {
|
||||
const timeAgo = (() => {
|
||||
|
|
@ -573,28 +585,10 @@ export default function Dashboard() {
|
|||
})();
|
||||
|
||||
const bgColor =
|
||||
task.status === 'completed' ? 'bg-emerald-50 border-emerald-200' :
|
||||
task.status === 'running' ? 'bg-blue-50 border-blue-200' :
|
||||
task.status === 'failed' ? 'bg-red-50 border-red-200' :
|
||||
'bg-gray-50 border-gray-200';
|
||||
|
||||
const textColor =
|
||||
task.status === 'completed' ? 'text-emerald-900' :
|
||||
task.status === 'running' ? 'text-blue-900' :
|
||||
task.status === 'failed' ? 'text-red-900' :
|
||||
'text-gray-900';
|
||||
|
||||
const descColor =
|
||||
task.status === 'completed' ? 'text-emerald-700' :
|
||||
task.status === 'running' ? 'text-blue-700' :
|
||||
task.status === 'failed' ? 'text-red-700' :
|
||||
'text-gray-700';
|
||||
|
||||
const timeColor =
|
||||
task.status === 'completed' ? 'text-emerald-600' :
|
||||
task.status === 'running' ? 'text-blue-600' :
|
||||
task.status === 'failed' ? 'text-red-600' :
|
||||
'text-gray-600';
|
||||
task.status === 'completed' ? 'bg-green-50 border-black' :
|
||||
task.status === 'running' ? 'bg-blue-50 border-black' :
|
||||
task.status === 'failed' ? 'bg-red-50 border-black' :
|
||||
'bg-gray-50 border-black';
|
||||
|
||||
const statusText =
|
||||
task.status === 'completed' ? '任务完成' :
|
||||
|
|
@ -606,53 +600,52 @@ export default function Dashboard() {
|
|||
<Link
|
||||
key={task.id}
|
||||
to={`/tasks/${task.id}`}
|
||||
className={`block p-3 rounded-lg border ${bgColor} hover:shadow-sm transition-shadow`}
|
||||
className={`block p-3 border-2 ${bgColor} hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all`}
|
||||
>
|
||||
<p className={`text-sm font-medium ${textColor}`}>{statusText}</p>
|
||||
<p className={`text-xs ${descColor} mt-1 line-clamp-1`}>
|
||||
<p className="text-sm font-bold font-mono uppercase text-black">{statusText}</p>
|
||||
<p className="text-xs text-gray-700 mt-1 line-clamp-1 font-mono">
|
||||
项目 "{task.project?.name || '未知项目'}"
|
||||
{task.status === 'completed' && task.issues_count > 0 &&
|
||||
` - 发现 ${task.issues_count} 个问题`
|
||||
}
|
||||
</p>
|
||||
<p className={`text-xs ${timeColor} mt-1`}>{timeAgo}</p>
|
||||
<p className="text-xs text-gray-500 mt-1 font-mono">{timeAgo}</p>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
<div className="text-center py-8 text-gray-400 border-2 border-dashed border-black">
|
||||
<Clock className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">暂无活动记录</p>
|
||||
<p className="text-sm font-mono uppercase">暂无活动记录</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 使用技巧 */}
|
||||
<Card className="card-modern bg-gradient-to-br from-purple-50 to-pink-50 border border-purple-100/50">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg flex items-center">
|
||||
{/* 使用技巧 */}
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="pb-3 border-b-2 border-black mb-4">
|
||||
<h3 className="flex items-center text-lg font-display font-bold uppercase">
|
||||
<Target className="w-5 h-5 mr-2 text-purple-600" />
|
||||
使用技巧
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start space-x-2">
|
||||
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-sm text-gray-700">定期运行代码审计可以及早发现潜在问题</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-sm text-gray-700">使用即时分析功能快速检查代码片段</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
<div className="w-2 h-2 bg-purple-400 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-sm text-gray-700">关注质量评分趋势,持续改进代码质量</p>
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-3 font-mono text-sm">
|
||||
<div className="flex items-start space-x-2">
|
||||
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700">定期运行代码审计可以及早发现潜在问题</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex items-start space-x-2">
|
||||
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700">使用即时分析功能快速检查代码片段</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
<div className="w-2 h-2 bg-black mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700">关注质量评分趋势,持续改进代码质量</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
|
@ -397,28 +397,28 @@ class UserManager {
|
|||
return { task: tempTask, issues: tempIssues };
|
||||
};
|
||||
|
||||
// 渲染问题的函数,使用紧凑样式
|
||||
// 渲染问题的函数,使用复古样式
|
||||
const renderIssue = (issue: any, index: number) => (
|
||||
<div key={index} className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-gray-300 transition-all duration-200 group">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div key={index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 mb-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-start justify-between mb-3 pb-3 border-b-2 border-dashed border-gray-300">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
|
||||
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
|
||||
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
|
||||
'bg-blue-100 text-blue-600'
|
||||
<div className={`w-8 h-8 border-2 border-black flex items-center justify-center shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${issue.severity === 'critical' ? 'bg-red-500 text-white' :
|
||||
issue.severity === 'high' ? 'bg-orange-500 text-white' :
|
||||
issue.severity === 'medium' ? 'bg-yellow-400 text-black' :
|
||||
'bg-blue-400 text-white'
|
||||
}`}>
|
||||
{getTypeIcon(issue.type)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-semibold text-base text-gray-900 mb-1 group-hover:text-gray-700 transition-colors">{issue.title}</h4>
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-600">
|
||||
<h4 className="font-bold text-base text-black mb-1 font-mono uppercase">{issue.title}</h4>
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-600 font-mono">
|
||||
<span>📍</span>
|
||||
<span>第 {issue.line} 行</span>
|
||||
{issue.column && <span>,第 {issue.column} 列</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`${getSeverityColor(issue.severity)} px-2 py-1 text-xs font-medium`}>
|
||||
<Badge className={`rounded-none border-2 border-black ${getSeverityColor(issue.severity)} font-bold uppercase`}>
|
||||
{issue.severity === 'critical' ? '严重' :
|
||||
issue.severity === 'high' ? '高' :
|
||||
issue.severity === 'medium' ? '中等' : '低'}
|
||||
|
|
@ -426,46 +426,43 @@ class UserManager {
|
|||
</div>
|
||||
|
||||
{issue.description && (
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3 mb-3">
|
||||
<div className="flex items-center mb-1">
|
||||
<Info className="w-3 h-3 text-gray-600 mr-1" />
|
||||
<span className="font-medium text-gray-800 text-xs">问题详情</span>
|
||||
<div className="bg-gray-50 border-2 border-black p-3 mb-3 font-mono text-xs">
|
||||
<div className="flex items-center mb-1 border-b-2 border-black pb-1 w-fit">
|
||||
<Info className="w-3 h-3 text-black mr-1" />
|
||||
<span className="font-bold text-black uppercase">问题详情</span>
|
||||
</div>
|
||||
<p className="text-gray-700 text-xs leading-relaxed">
|
||||
<p className="text-gray-800 leading-relaxed mt-2">
|
||||
{issue.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{issue.code_snippet && (
|
||||
<div className="bg-gray-900 rounded-lg p-3 mb-3 border border-gray-700">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="bg-black text-green-400 p-3 mb-3 border-2 border-gray-800 font-mono text-xs relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 bg-gray-800 text-white px-2 py-0.5 text-[10px] uppercase">Code</div>
|
||||
<div className="flex items-center justify-between mb-2 border-b border-gray-800 pb-1">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-4 h-4 bg-red-600 rounded flex items-center justify-center">
|
||||
<Code className="w-2 h-2 text-white" />
|
||||
</div>
|
||||
<span className="text-gray-300 text-xs font-medium">问题代码</span>
|
||||
<Code className="w-3 h-3 text-green-500" />
|
||||
<span className="text-gray-400 font-bold uppercase">问题代码</span>
|
||||
</div>
|
||||
<span className="text-gray-400 text-xs">第 {issue.line} 行</span>
|
||||
</div>
|
||||
<div className="bg-black/40 rounded p-2">
|
||||
<pre className="text-xs text-gray-100 overflow-x-auto">
|
||||
<code>{issue.code_snippet}</code>
|
||||
</pre>
|
||||
<span className="text-gray-500">Line {issue.line}</span>
|
||||
</div>
|
||||
<pre className="overflow-x-auto">
|
||||
<code>{issue.code_snippet}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{issue.suggestion && (
|
||||
<div className="bg-white border border-blue-200 rounded-lg p-3 shadow-sm">
|
||||
<div className="bg-blue-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="w-5 h-5 bg-blue-600 rounded flex items-center justify-center mr-2">
|
||||
<Lightbulb className="w-3 h-3 text-white" />
|
||||
<div className="w-5 h-5 bg-blue-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||
<Lightbulb className="w-3 h-3" />
|
||||
</div>
|
||||
<span className="font-medium text-blue-800 text-sm">修复建议</span>
|
||||
<span className="font-bold text-blue-900 text-sm uppercase font-mono">修复建议</span>
|
||||
</div>
|
||||
<p className="text-blue-700 text-xs leading-relaxed">{issue.suggestion}</p>
|
||||
<p className="text-blue-900 text-xs leading-relaxed font-mono">{issue.suggestion}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -474,44 +471,44 @@ class UserManager {
|
|||
|
||||
if (parsedExplanation) {
|
||||
return (
|
||||
<div className="bg-white border border-red-200 rounded-lg p-3 shadow-sm">
|
||||
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="w-5 h-5 bg-red-600 rounded flex items-center justify-center mr-2">
|
||||
<Zap className="w-3 h-3 text-white" />
|
||||
<div className="w-5 h-5 bg-red-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||
<Zap className="w-3 h-3" />
|
||||
</div>
|
||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
||||
<span className="font-bold text-red-900 text-sm uppercase font-mono">AI 解释</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="space-y-2 text-xs font-mono">
|
||||
{parsedExplanation.what && (
|
||||
<div className="border-l-2 border-red-600 pl-2">
|
||||
<span className="font-medium text-red-700">问题:</span>
|
||||
<span className="text-gray-700 ml-1">{parsedExplanation.what}</span>
|
||||
<div className="border-l-4 border-red-600 pl-2">
|
||||
<span className="font-bold text-red-900 uppercase block mb-1">问题:</span>
|
||||
<span className="text-gray-800">{parsedExplanation.what}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parsedExplanation.why && (
|
||||
<div className="border-l-2 border-gray-600 pl-2">
|
||||
<span className="font-medium text-gray-700">原因:</span>
|
||||
<span className="text-gray-700 ml-1">{parsedExplanation.why}</span>
|
||||
<div className="border-l-4 border-gray-600 pl-2">
|
||||
<span className="font-bold text-gray-900 uppercase block mb-1">原因:</span>
|
||||
<span className="text-gray-800">{parsedExplanation.why}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parsedExplanation.how && (
|
||||
<div className="border-l-2 border-black pl-2">
|
||||
<span className="font-medium text-black">方案:</span>
|
||||
<span className="text-gray-700 ml-1">{parsedExplanation.how}</span>
|
||||
<div className="border-l-4 border-black pl-2">
|
||||
<span className="font-bold text-black uppercase block mb-1">方案:</span>
|
||||
<span className="text-gray-800">{parsedExplanation.how}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parsedExplanation.learn_more && (
|
||||
<div className="border-l-2 border-red-400 pl-2">
|
||||
<span className="font-medium text-red-600">链接:</span>
|
||||
<div className="border-l-4 border-blue-400 pl-2">
|
||||
<span className="font-bold text-blue-900 uppercase block mb-1">链接:</span>
|
||||
<a
|
||||
href={parsedExplanation.learn_more}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-red-600 hover:text-red-800 hover:underline ml-1"
|
||||
className="text-blue-700 hover:text-blue-900 hover:underline break-all"
|
||||
>
|
||||
{parsedExplanation.learn_more}
|
||||
</a>
|
||||
|
|
@ -523,12 +520,12 @@ class UserManager {
|
|||
} else {
|
||||
// 如果无法解析JSON,回退到原始显示方式
|
||||
return (
|
||||
<div className="bg-white border border-red-200 rounded-lg p-3">
|
||||
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center mb-2">
|
||||
<Zap className="w-4 h-4 text-red-600 mr-2" />
|
||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
||||
<span className="font-bold text-red-900 text-sm uppercase font-mono">AI 解释</span>
|
||||
</div>
|
||||
<p className="text-gray-700 text-xs leading-relaxed">{issue.ai_explanation}</p>
|
||||
<p className="text-gray-800 text-xs leading-relaxed font-mono">{issue.ai_explanation}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -538,35 +535,37 @@ class UserManager {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<div className="space-y-6 animate-fade-in font-mono">
|
||||
{/* 页面标题 */}
|
||||
<div>
|
||||
<h1 className="page-title">即时代码分析</h1>
|
||||
<p className="page-subtitle">快速分析代码片段,发现潜在问题并获得修复建议</p>
|
||||
<div className="border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">即时代码分析</h1>
|
||||
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">快速分析代码片段,发现潜在问题并获得修复建议</p>
|
||||
</div>
|
||||
|
||||
{/* 代码输入区域 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base">代码分析</CardTitle>
|
||||
{result && (
|
||||
<Button variant="outline" onClick={clearAnalysis} size="sm">
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
重新分析
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||
<Code className="w-5 h-5 mr-2" />
|
||||
代码分析
|
||||
</h3>
|
||||
{result && (
|
||||
<Button variant="outline" onClick={clearAnalysis} size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-8">
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
重新分析
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
{/* 工具栏 */}
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1">
|
||||
<Select value={language} onValueChange={setLanguage}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectTrigger className="h-10 retro-input rounded-none border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] focus:ring-0">
|
||||
<SelectValue placeholder="选择编程语言" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent className="rounded-none border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
{supportedLanguages.map((lang) => (
|
||||
<SelectItem key={lang} value={lang}>
|
||||
{lang.charAt(0).toUpperCase() + lang.slice(1)}
|
||||
|
|
@ -579,9 +578,9 @@ class UserManager {
|
|||
variant="outline"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={analyzing}
|
||||
size="sm"
|
||||
className="retro-btn bg-white text-black hover:bg-gray-100 h-10"
|
||||
>
|
||||
<Upload className="w-3 h-3 mr-1" />
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
上传文件
|
||||
</Button>
|
||||
<input
|
||||
|
|
@ -594,14 +593,14 @@ class UserManager {
|
|||
</div>
|
||||
|
||||
{/* 快速示例 */}
|
||||
<div className="flex flex-wrap gap-2 items-center">
|
||||
<span className="text-xs text-gray-600">示例:</span>
|
||||
<div className="flex flex-wrap gap-2 items-center p-2 bg-gray-50 border-2 border-dashed border-gray-300">
|
||||
<span className="text-xs font-bold uppercase text-gray-600 mr-2">示例:</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => loadExampleCode('javascript')}
|
||||
disabled={analyzing}
|
||||
className="h-7 px-2 text-xs"
|
||||
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-yellow-100"
|
||||
>
|
||||
JavaScript
|
||||
</Button>
|
||||
|
|
@ -610,7 +609,7 @@ class UserManager {
|
|||
size="sm"
|
||||
onClick={() => loadExampleCode('python')}
|
||||
disabled={analyzing}
|
||||
className="h-7 px-2 text-xs"
|
||||
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-blue-100"
|
||||
>
|
||||
Python
|
||||
</Button>
|
||||
|
|
@ -619,7 +618,7 @@ class UserManager {
|
|||
size="sm"
|
||||
onClick={() => loadExampleCode('java')}
|
||||
disabled={analyzing}
|
||||
className="h-7 px-2 text-xs"
|
||||
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-red-100"
|
||||
>
|
||||
Java
|
||||
</Button>
|
||||
|
|
@ -628,7 +627,7 @@ class UserManager {
|
|||
size="sm"
|
||||
onClick={() => loadExampleCode('swift')}
|
||||
disabled={analyzing}
|
||||
className="h-7 px-2 text-xs"
|
||||
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-orange-100"
|
||||
>
|
||||
Swift
|
||||
</Button>
|
||||
|
|
@ -637,22 +636,25 @@ class UserManager {
|
|||
size="sm"
|
||||
onClick={() => loadExampleCode('kotlin')}
|
||||
disabled={analyzing}
|
||||
className="h-7 px-2 text-xs"
|
||||
className="h-7 px-2 text-xs retro-btn bg-white hover:bg-purple-100"
|
||||
>
|
||||
Kotlin
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 代码编辑器 */}
|
||||
<div>
|
||||
<div className="relative">
|
||||
<div className="absolute top-0 right-0 bg-black text-white px-2 py-1 text-xs font-mono uppercase z-10 border-l-2 border-b-2 border-white">
|
||||
Editor
|
||||
</div>
|
||||
<Textarea
|
||||
placeholder="粘贴代码或上传文件..."
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
className="min-h-[250px] font-mono text-sm"
|
||||
className="min-h-[300px] font-mono text-sm retro-input bg-gray-900 text-green-400 border-2 border-black p-4 focus:ring-0 focus:border-primary"
|
||||
disabled={analyzing}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
<div className="text-xs text-gray-500 mt-1 font-mono text-right">
|
||||
{code.length} 字符,{code.split('\n').length} 行
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -661,176 +663,174 @@ class UserManager {
|
|||
<Button
|
||||
onClick={handleAnalyze}
|
||||
disabled={!code.trim() || !language || analyzing}
|
||||
className="w-full btn-primary"
|
||||
className="w-full retro-btn bg-primary text-white hover:bg-primary/90 h-12 text-lg font-bold uppercase"
|
||||
>
|
||||
{analyzing ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
<div className="animate-spin rounded-none h-5 w-5 border-4 border-white border-t-transparent mr-3"></div>
|
||||
分析中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
<Zap className="w-5 h-5 mr-2" />
|
||||
开始分析
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 分析结果区域 */}
|
||||
{result && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
{/* 结果概览 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center text-base">
|
||||
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
|
||||
分析结果
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{analysisTime.toFixed(2)}s
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{language.charAt(0).toUpperCase() + language.slice(1)}
|
||||
</Badge>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
|
||||
分析结果
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{analysisTime.toFixed(2)}s
|
||||
</Badge>
|
||||
<Badge variant="outline" className="rounded-none border-black bg-white text-xs font-mono uppercase">
|
||||
{language}
|
||||
</Badge>
|
||||
|
||||
{/* 导出按钮 */}
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setExportDialogOpen(true)}
|
||||
className="btn-primary"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
导出报告
|
||||
</Button>
|
||||
</div>
|
||||
{/* 导出按钮 */}
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setExportDialogOpen(true)}
|
||||
className="retro-btn bg-primary text-white hover:bg-primary/90 h-8"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
导出报告
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
{/* 核心指标 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div className="text-center p-4 bg-white rounded-lg border border-red-200">
|
||||
<div className="w-12 h-12 bg-primary rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Target className="w-6 h-6 text-white" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 font-mono">
|
||||
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="w-12 h-12 bg-primary border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Target className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-primary mb-1">
|
||||
<div className="text-3xl font-bold text-primary mb-1">
|
||||
{result.quality_score.toFixed(1)}
|
||||
</div>
|
||||
<p className="text-xs font-medium text-primary/80 mb-2">质量评分</p>
|
||||
<Progress value={result.quality_score} className="h-1" />
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-2">质量评分</p>
|
||||
<Progress value={result.quality_score} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-white rounded-lg border border-red-200">
|
||||
<div className="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<AlertTriangle className="w-6 h-6 text-white" />
|
||||
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="w-12 h-12 bg-red-600 border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-red-600 mb-1">
|
||||
<div className="text-3xl font-bold text-red-600 mb-1">
|
||||
{result.summary.critical_issues + result.summary.high_issues}
|
||||
</div>
|
||||
<p className="text-xs font-medium text-red-700 mb-1">严重问题</p>
|
||||
<div className="text-xs text-red-600">需要立即处理</div>
|
||||
<p className="text-xs font-bold text-red-700 uppercase mb-1">严重问题</p>
|
||||
<div className="text-xs text-red-600 font-bold uppercase">需要立即处理</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-white rounded-lg border border-yellow-200">
|
||||
<div className="w-12 h-12 bg-yellow-600 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Info className="w-6 h-6 text-white" />
|
||||
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="w-12 h-12 bg-yellow-400 border-2 border-black flex items-center justify-center mx-auto mb-3 text-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Info className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-yellow-600 mb-1">
|
||||
<div className="text-3xl font-bold text-yellow-600 mb-1">
|
||||
{result.summary.medium_issues + result.summary.low_issues}
|
||||
</div>
|
||||
<p className="text-xs font-medium text-yellow-700 mb-1">一般问题</p>
|
||||
<div className="text-xs text-yellow-600">建议优化</div>
|
||||
<p className="text-xs font-bold text-yellow-700 uppercase mb-1">一般问题</p>
|
||||
<div className="text-xs text-yellow-600 font-bold uppercase">建议优化</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-white rounded-lg border border-green-200">
|
||||
<div className="w-12 h-12 bg-green-600 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<FileText className="w-6 h-6 text-white" />
|
||||
<div className="text-center p-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="w-12 h-12 bg-green-600 border-2 border-black flex items-center justify-center mx-auto mb-3 text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<FileText className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">
|
||||
<div className="text-3xl font-bold text-green-600 mb-1">
|
||||
{result.issues.length}
|
||||
</div>
|
||||
<p className="text-xs font-medium text-green-700 mb-1">总问题数</p>
|
||||
<div className="text-xs text-green-600">已全部识别</div>
|
||||
<p className="text-xs font-bold text-green-700 uppercase mb-1">总问题数</p>
|
||||
<div className="text-xs text-green-600 font-bold uppercase">已全部识别</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 详细指标 */}
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-3 flex items-center">
|
||||
<TrendingUp className="w-4 h-4 mr-1" />
|
||||
<div className="bg-gray-50 border-2 border-black p-4 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<h3 className="text-sm font-bold text-black uppercase mb-4 flex items-center font-mono border-b-2 border-black pb-2 w-fit">
|
||||
<TrendingUp className="w-4 h-4 mr-2" />
|
||||
详细指标
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.complexity}</div>
|
||||
<p className="text-xs text-gray-600 mb-2">复杂度</p>
|
||||
<Progress value={result.metrics.complexity} className="h-1" />
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.complexity}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">复杂度</p>
|
||||
<Progress value={result.metrics.complexity} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.maintainability}</div>
|
||||
<p className="text-xs text-gray-600 mb-2">可维护性</p>
|
||||
<Progress value={result.metrics.maintainability} className="h-1" />
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.maintainability}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">可维护性</p>
|
||||
<Progress value={result.metrics.maintainability} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.security}</div>
|
||||
<p className="text-xs text-gray-600 mb-2">安全性</p>
|
||||
<Progress value={result.metrics.security} className="h-1" />
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.security}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">安全性</p>
|
||||
<Progress value={result.metrics.security} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-bold text-gray-900 mb-1">{result.metrics.performance}</div>
|
||||
<p className="text-xs text-gray-600 mb-2">性能</p>
|
||||
<Progress value={result.metrics.performance} className="h-1" />
|
||||
<div className="text-xl font-bold text-black mb-1">{result.metrics.performance}</div>
|
||||
<p className="text-xs text-gray-600 uppercase mb-2">性能</p>
|
||||
<Progress value={result.metrics.performance} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-black" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 问题详情 */}
|
||||
<Card className="card-modern">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center text-base">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||
<Shield className="w-5 h-5 mr-2 text-orange-600" />
|
||||
发现的问题 ({result.issues.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
{result.issues.length > 0 ? (
|
||||
<Tabs defaultValue="all" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-4 mb-4">
|
||||
<TabsTrigger value="all" className="text-xs">
|
||||
<TabsList className="grid w-full grid-cols-4 mb-6 bg-transparent border-2 border-black p-0 h-auto gap-0">
|
||||
<TabsTrigger value="all" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
全部 ({result.issues.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="critical" className="text-xs">
|
||||
<TabsTrigger value="critical" className="rounded-none border-r-2 border-black data-[state=active]:bg-red-600 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
严重 ({result.issues.filter(i => i.severity === 'critical').length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="high" className="text-xs">
|
||||
<TabsTrigger value="high" className="rounded-none border-r-2 border-black data-[state=active]:bg-orange-500 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
高 ({result.issues.filter(i => i.severity === 'high').length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="medium" className="text-xs">
|
||||
<TabsTrigger value="medium" className="rounded-none data-[state=active]:bg-yellow-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
|
||||
中等 ({result.issues.filter(i => i.severity === 'medium').length})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="all" className="space-y-3 mt-4">
|
||||
<TabsContent value="all" className="space-y-4 mt-0">
|
||||
{result.issues.map((issue, index) => renderIssue(issue, index))}
|
||||
</TabsContent>
|
||||
|
||||
{['critical', 'high', 'medium'].map(severity => (
|
||||
<TabsContent key={severity} value={severity} className="space-y-3 mt-4">
|
||||
<TabsContent key={severity} value={severity} className="space-y-4 mt-0">
|
||||
{result.issues.filter(issue => issue.severity === severity).length > 0 ? (
|
||||
result.issues.filter(issue => issue.severity === severity).map((issue, index) => renderIssue(issue, index))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">
|
||||
没有发现{severity === 'critical' ? '严重' : severity === 'high' ? '高优先级' : '中等优先级'}问题
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
<p className="text-gray-500 font-mono">
|
||||
代码在此级别的检查中表现良好
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -838,47 +838,45 @@ class UserManager {
|
|||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
|
||||
) : (
|
||||
<div className="text-center py-16">
|
||||
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<div className="text-center py-16 border-2 border-dashed border-black bg-green-50">
|
||||
<div className="w-20 h-20 bg-green-100 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<CheckCircle className="w-12 h-12 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-green-800 mb-3">代码质量优秀!</h3>
|
||||
<p className="text-green-600 text-lg mb-6">恭喜!没有发现任何问题</p>
|
||||
<div className="bg-green-50 rounded-lg p-6 max-w-md mx-auto">
|
||||
<p className="text-green-700 text-sm">
|
||||
<h3 className="text-2xl font-display font-bold text-green-800 mb-3 uppercase">代码质量优秀!</h3>
|
||||
<p className="text-green-700 text-lg mb-6 font-mono font-bold">恭喜!没有发现任何问题</p>
|
||||
<div className="bg-white border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p className="text-black text-sm font-mono">
|
||||
您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分析进行中状态 */}
|
||||
{analyzing && (
|
||||
<Card className="card-modern">
|
||||
<CardContent className="py-16">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="py-16 px-6">
|
||||
<div ref={loadingCardRef} className="text-center">
|
||||
<div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"></div>
|
||||
<div className="w-20 h-20 bg-red-50 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="animate-spin rounded-none h-12 w-12 border-4 border-primary border-t-transparent"></div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">AI正在分析您的代码</h3>
|
||||
<p className="text-gray-600 text-lg mb-6">请稍候,这通常需要至少30秒钟...</p>
|
||||
<p className="text-gray-600 text-lg mb-6">分析时长取决于您的网络环境、代码长度以及使用的模型等因素</p>
|
||||
<div className="bg-red-50 rounded-lg p-6 max-w-md mx-auto">
|
||||
<p className="text-red-700 text-sm">
|
||||
<h3 className="text-2xl font-display font-bold text-black uppercase mb-3">AI正在分析您的代码</h3>
|
||||
<p className="text-gray-600 text-lg mb-6 font-mono">请稍候,这通常需要至少30秒钟...</p>
|
||||
<p className="text-gray-600 text-sm mb-6 font-mono">分析时长取决于您的网络环境、代码长度以及使用的模型等因素</p>
|
||||
<div className="bg-red-50 border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p className="text-red-700 text-sm font-mono font-bold">
|
||||
正在进行安全检测、性能分析、代码风格检查等多维度评估<br />
|
||||
请勿离开页面!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 导出报告对话框 */}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import { apiClient } from '@/shared/api/serverClient';
|
|||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
import { Terminal, Lock, Cpu } from 'lucide-react';
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState('');
|
||||
|
|
@ -32,64 +33,111 @@ export default function Login() {
|
|||
const formData = new URLSearchParams();
|
||||
formData.append('username', email);
|
||||
formData.append('password', password);
|
||||
|
||||
|
||||
const response = await apiClient.post('/auth/login', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
await login(response.data.access_token);
|
||||
toast.success('登录成功');
|
||||
toast.success('访问已授予');
|
||||
// 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data?.detail || '登录失败');
|
||||
toast.error(error.response?.data?.detail || '访问被拒绝');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl font-bold text-center">XCodeReviewer</CardTitle>
|
||||
<CardDescription className="text-center">请输入您的账号和密码登录</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="min-h-screen flex items-center justify-center bg-background relative overflow-hidden">
|
||||
{/* Decorative Background Elements */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
|
||||
|
||||
<div className="absolute top-10 left-10 font-mono text-xs text-gray-400 hidden md:block">
|
||||
<div>系统ID: 0x84F2</div>
|
||||
<div>状态: 等待输入</div>
|
||||
<div>加密: AES-256</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-10 right-10 font-mono text-xs text-gray-400 hidden md:block text-right">
|
||||
<div>安全连接</div>
|
||||
<div>端口: 443</div>
|
||||
</div>
|
||||
|
||||
{/* Main Card */}
|
||||
<div className="w-full max-w-md relative z-10 p-4">
|
||||
<div className="mb-8 text-center">
|
||||
<div className="inline-flex items-center justify-center p-4 bg-primary border-2 border-black shadow-retro mb-4">
|
||||
<Terminal className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-display font-bold tracking-tighter uppercase">
|
||||
XCode<span className="text-primary">Reviewer</span>
|
||||
</h1>
|
||||
<p className="text-sm font-mono text-gray-500 mt-2">输入凭据以继续</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border-2 border-black shadow-retro p-8 relative">
|
||||
{/* Decorative Corner Markers */}
|
||||
<div className="absolute top-2 left-2 w-2 h-2 bg-black" />
|
||||
<div className="absolute top-2 right-2 w-2 h-2 bg-black" />
|
||||
<div className="absolute bottom-2 left-2 w-2 h-2 bg-black" />
|
||||
<div className="absolute bottom-2 right-2 w-2 h-2 bg-black" />
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6 mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">邮箱</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="name@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="email" className="font-mono uppercase text-xs font-bold">身份 / 邮箱</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="USER@DOMAIN.COM"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="retro-input font-mono pl-10"
|
||||
/>
|
||||
<Cpu className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="password" className="font-mono uppercase text-xs font-bold">通行码</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="retro-input font-mono pl-10"
|
||||
/>
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? '登录中...' : '登录'}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full retro-btn text-lg h-12 mt-4"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="animate-spin">/</span> 认证中...
|
||||
</span>
|
||||
) : '初始化会话'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col space-y-2">
|
||||
<div className="text-sm text-center text-gray-500">
|
||||
还没有账号? <span className="text-blue-600 cursor-pointer hover:underline" onClick={() => navigate('/register')}>立即注册</span>
|
||||
|
||||
<div className="mt-6 pt-6 border-t-2 border-dashed border-gray-200 text-center">
|
||||
<div className="text-xs font-mono text-gray-500">
|
||||
没有访问令牌? <span className="text-primary font-bold cursor-pointer hover:underline" onClick={() => navigate('/register')}>申请访问</span>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -76,14 +76,14 @@ export default function RecycleBin() {
|
|||
try {
|
||||
// 删除项目数据
|
||||
await api.permanentlyDeleteProject(selectedProject.id);
|
||||
|
||||
|
||||
// 删除保存的ZIP文件(如果有)
|
||||
try {
|
||||
await deleteZipFile(selectedProject.id);
|
||||
} catch (error) {
|
||||
console.error('删除ZIP文件失败:', error);
|
||||
}
|
||||
|
||||
|
||||
toast.success(`项目 "${selectedProject.name}" 已永久删除`);
|
||||
setShowPermanentDeleteDialog(false);
|
||||
setSelectedProject(null);
|
||||
|
|
@ -113,10 +113,10 @@ export default function RecycleBin() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
|
||||
<p className="text-gray-500">加载中...</p>
|
||||
<div className="animate-spin rounded-none h-12 w-12 border-4 border-black border-t-transparent mx-auto mb-4"></div>
|
||||
<p className="text-black font-mono font-bold uppercase">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -125,72 +125,68 @@ export default function RecycleBin() {
|
|||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||
<div>
|
||||
<h1 className="page-title flex items-center gap-2">
|
||||
<Trash2 className="w-8 h-8 text-gray-400" />
|
||||
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter flex items-center gap-2">
|
||||
<Trash2 className="w-8 h-8 text-black" />
|
||||
回收站
|
||||
</h1>
|
||||
<p className="page-subtitle">管理已删除的项目,可以恢复或永久删除</p>
|
||||
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">管理已删除的项目,可以恢复或永久删除</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索 */}
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="搜索已删除的项目..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" />
|
||||
<Input
|
||||
placeholder="搜索已删除的项目..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 retro-input h-10 bg-gray-50 border-2 border-black text-black placeholder:text-gray-500 focus:ring-0 focus:border-primary rounded-none font-mono"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 项目列表 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredProjects.length > 0 ? (
|
||||
filteredProjects.map((project) => (
|
||||
<Card key={project.id} className="card-modern group opacity-75 hover:opacity-100 transition-opacity">
|
||||
<CardHeader className="pb-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-gray-400 to-gray-500 flex items-center justify-center text-white text-lg">
|
||||
{getRepositoryIcon(project.repository_type)}
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg">
|
||||
{project.name}
|
||||
</CardTitle>
|
||||
{project.description && (
|
||||
<p className="text-sm text-gray-500 mt-1 line-clamp-2">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div key={project.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all group">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50 flex items-start justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 border-2 border-black bg-white flex items-center justify-center text-black text-lg shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
{getRepositoryIcon(project.repository_type)}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold font-display uppercase truncate max-w-[150px]">
|
||||
{project.name}
|
||||
</h3>
|
||||
{project.description && (
|
||||
<p className="text-xs text-gray-500 mt-1 line-clamp-1 font-mono">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700">
|
||||
已删除
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<Badge variant="secondary" className="flex-shrink-0 bg-red-100 text-red-700 border-2 border-black rounded-none font-bold uppercase text-xs">
|
||||
已删除
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
<div className="p-4 space-y-4 font-mono">
|
||||
{/* 项目信息 */}
|
||||
<div className="space-y-3">
|
||||
{project.repository_url && (
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<div className="flex items-center text-xs text-gray-600 font-bold">
|
||||
<GitBranch className="w-4 h-4 mr-2 flex-shrink-0" />
|
||||
<a
|
||||
href={project.repository_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-primary transition-colors flex items-center truncate"
|
||||
className="hover:text-primary transition-colors flex items-center truncate hover:underline"
|
||||
>
|
||||
<span className="truncate">{project.repository_url.replace('https://', '')}</span>
|
||||
<ExternalLink className="w-3 h-3 ml-1 flex-shrink-0" />
|
||||
|
|
@ -198,7 +194,7 @@ export default function RecycleBin() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<div className="flex items-center justify-between text-xs text-gray-500 font-medium">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-2" />
|
||||
删除于 {formatDate(project.updated_at)}
|
||||
|
|
@ -214,12 +210,12 @@ export default function RecycleBin() {
|
|||
{project.programming_languages && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{JSON.parse(project.programming_languages).slice(0, 4).map((lang: string) => (
|
||||
<Badge key={lang} variant="outline" className="text-xs">
|
||||
<Badge key={lang} variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
|
||||
{lang}
|
||||
</Badge>
|
||||
))}
|
||||
{JSON.parse(project.programming_languages).length > 4 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Badge variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
|
||||
+{JSON.parse(project.programming_languages).length - 4}
|
||||
</Badge>
|
||||
)}
|
||||
|
|
@ -227,11 +223,11 @@ export default function RecycleBin() {
|
|||
)}
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex gap-2 pt-2">
|
||||
<div className="flex gap-2 pt-2 border-t-2 border-black mt-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex-1 text-green-600 hover:text-green-700 hover:bg-green-50"
|
||||
className="flex-1 text-green-700 hover:text-white hover:bg-green-600 border-2 border-black rounded-none h-9 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
|
||||
onClick={() => handleRestoreClick(project)}
|
||||
>
|
||||
<RotateCcw className="w-4 h-4 mr-2" />
|
||||
|
|
@ -240,52 +236,55 @@ export default function RecycleBin() {
|
|||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex-1 text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
className="flex-1 text-red-700 hover:text-white hover:bg-red-600 border-2 border-black rounded-none h-9 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:shadow-[1px_1px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[1px] hover:translate-y-[1px] transition-all"
|
||||
onClick={() => handlePermanentDeleteClick(project)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
永久删除
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-full">
|
||||
<Card className="card-modern">
|
||||
<CardContent className="empty-state py-16">
|
||||
<div className="empty-icon">
|
||||
<Inbox className="w-8 h-8 text-primary" />
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="py-16 flex flex-col items-center justify-center text-center">
|
||||
<div className="w-20 h-20 bg-gray-100 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Inbox className="w-10 h-10 text-gray-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">
|
||||
{searchTerm ? '未找到匹配的项目' : '回收站为空'}
|
||||
</h3>
|
||||
<p className="text-gray-500 mb-6 max-w-md">
|
||||
<p className="text-gray-500 font-mono max-w-md">
|
||||
{searchTerm ? '尝试调整搜索条件' : '回收站中没有已删除的项目'}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 恢复项目确认对话框 */}
|
||||
<AlertDialog open={showRestoreDialog} onOpenChange={setShowRestoreDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认恢复项目</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
您确定要恢复项目 <span className="font-semibold text-gray-900">"{selectedProject?.name}"</span> 吗?
|
||||
<AlertDialogContent className="retro-card border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-0 bg-white max-w-md">
|
||||
<AlertDialogHeader className="p-6 border-b-2 border-black bg-gray-50">
|
||||
<AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2">
|
||||
<RotateCcw className="w-6 h-6 text-green-600" />
|
||||
确认恢复项目
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
|
||||
您确定要恢复项目 <span className="font-bold text-black">"{selectedProject?.name}"</span> 吗?
|
||||
<br />
|
||||
<br />
|
||||
恢复后,该项目将重新出现在项目列表中,您可以继续使用该项目的所有功能。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogFooter className="p-6 bg-white flex gap-3">
|
||||
<AlertDialogCancel className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase">取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleConfirmRestore}
|
||||
className="bg-green-600 hover:bg-green-700 focus:ring-green-600"
|
||||
className="retro-btn bg-green-600 text-white border-2 border-black hover:bg-green-700 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]"
|
||||
>
|
||||
确认恢复
|
||||
</AlertDialogAction>
|
||||
|
|
@ -295,19 +294,22 @@ export default function RecycleBin() {
|
|||
|
||||
{/* 永久删除确认对话框 */}
|
||||
<AlertDialog open={showPermanentDeleteDialog} onOpenChange={setShowPermanentDeleteDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2 text-red-600">
|
||||
<AlertTriangle className="w-5 h-5" />
|
||||
<AlertDialogContent className="retro-card border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] p-0 bg-white max-w-md">
|
||||
<AlertDialogHeader className="p-6 border-b-2 border-black bg-red-50">
|
||||
<AlertDialogTitle className="text-xl font-display font-bold uppercase flex items-center gap-2 text-red-600">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
警告:永久删除项目
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
您确定要<span className="font-semibold text-red-600">永久删除</span>项目 <span className="font-semibold text-gray-900">"{selectedProject?.name}"</span> 吗?
|
||||
<AlertDialogDescription className="mt-4 font-mono text-gray-600">
|
||||
您确定要<span className="font-bold text-red-600 uppercase">永久删除</span>项目 <span className="font-bold text-black">"{selectedProject?.name}"</span> 吗?
|
||||
<br />
|
||||
<br />
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 my-3">
|
||||
<p className="text-red-800 font-semibold mb-2">⚠️ 此操作不可撤销!</p>
|
||||
<ul className="list-disc list-inside text-red-700 space-y-1 text-sm">
|
||||
<div className="bg-red-100 border-2 border-red-500 p-4 my-3 shadow-[4px_4px_0px_0px_rgba(239,68,68,1)]">
|
||||
<p className="text-red-800 font-bold mb-2 uppercase flex items-center">
|
||||
<AlertTriangle className="w-4 h-4 mr-2" />
|
||||
此操作不可撤销!
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-red-700 space-y-1 text-xs font-bold">
|
||||
<li>项目数据将被永久删除</li>
|
||||
<li>相关的审计任务可能会受影响</li>
|
||||
<li>无法通过任何方式恢复</li>
|
||||
|
|
@ -315,11 +317,11 @@ export default function RecycleBin() {
|
|||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogFooter className="p-6 bg-white flex gap-3">
|
||||
<AlertDialogCancel className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-10 font-bold uppercase">取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleConfirmPermanentDelete}
|
||||
className="bg-red-600 hover:bg-red-700 focus:ring-red-600"
|
||||
className="retro-btn bg-red-600 text-white border-2 border-black hover:bg-red-700 rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_0px_rgba(0,0,0,1)]"
|
||||
>
|
||||
确认永久删除
|
||||
</AlertDialogAction>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
|
|
@ -21,8 +20,7 @@ import {
|
|||
Code,
|
||||
Lightbulb,
|
||||
Info,
|
||||
Zap,
|
||||
X
|
||||
Zap
|
||||
} from "lucide-react";
|
||||
import { api } from "@/shared/config/database";
|
||||
import type { AuditTask, AuditIssue } from "@/shared/types";
|
||||
|
|
@ -79,24 +77,24 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
const lowIssues = issues.filter(issue => issue.severity === 'low');
|
||||
|
||||
const renderIssue = (issue: AuditIssue, index: number) => (
|
||||
<div key={issue.id || index} className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md hover:border-gray-300 transition-all duration-200 group">
|
||||
<div key={issue.id || index} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all group">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
|
||||
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
|
||||
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
|
||||
'bg-blue-100 text-blue-600'
|
||||
}`}>
|
||||
<div className={`w - 10 h - 10 border - 2 border - black flex items - center justify - center shadow - [2px_2px_0px_0px_rgba(0, 0, 0, 1)] ${issue.severity === 'critical' ? 'bg-red-100 text-red-600' :
|
||||
issue.severity === 'high' ? 'bg-orange-100 text-orange-600' :
|
||||
issue.severity === 'medium' ? 'bg-yellow-100 text-yellow-600' :
|
||||
'bg-blue-100 text-blue-600'
|
||||
} `}>
|
||||
{getTypeIcon(issue.issue_type)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-semibold text-base text-gray-900 mb-1 group-hover:text-gray-700 transition-colors">{issue.title}</h4>
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-600">
|
||||
<h4 className="font-bold text-lg text-black mb-1 group-hover:text-primary transition-colors font-display uppercase">{issue.title}</h4>
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-600 font-mono">
|
||||
<FileText className="w-3 h-3" />
|
||||
<span className="font-medium">{issue.file_path}</span>
|
||||
<span className="font-bold">{issue.file_path}</span>
|
||||
</div>
|
||||
{issue.line_number && (
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-500 mt-1">
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-500 mt-1 font-mono">
|
||||
<span>📍</span>
|
||||
<span>第 {issue.line_number} 行</span>
|
||||
{issue.column_number && <span>,第 {issue.column_number} 列</span>}
|
||||
|
|
@ -104,7 +102,7 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`${getSeverityColor(issue.severity)} px-2 py-1 text-xs font-medium`}>
|
||||
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 2 py - 1 text - xs ${getSeverityColor(issue.severity)} `}>
|
||||
{issue.severity === 'critical' ? '严重' :
|
||||
issue.severity === 'high' ? '高' :
|
||||
issue.severity === 'medium' ? '中等' : '低'}
|
||||
|
|
@ -112,48 +110,48 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
</div>
|
||||
|
||||
{issue.description && (
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-3 mb-3">
|
||||
<div className="flex items-center mb-1">
|
||||
<Info className="w-3 h-3 text-gray-600 mr-1" />
|
||||
<span className="font-medium text-gray-800 text-xs">问题详情</span>
|
||||
<div className="bg-gray-50 border-2 border-black p-3 mb-3 font-mono">
|
||||
<div className="flex items-center mb-1 border-b-2 border-gray-200 pb-1">
|
||||
<Info className="w-3 h-3 text-black mr-1" />
|
||||
<span className="font-bold text-black text-xs uppercase">问题详情</span>
|
||||
</div>
|
||||
<p className="text-gray-700 text-xs leading-relaxed">
|
||||
<p className="text-gray-700 text-xs leading-relaxed mt-1">
|
||||
{issue.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{issue.code_snippet && (
|
||||
<div className="bg-gray-900 rounded-lg p-3 mb-3 border border-gray-700">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="bg-gray-900 p-3 mb-3 border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center justify-between mb-2 border-b border-gray-700 pb-1">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-4 h-4 bg-red-600 rounded flex items-center justify-center">
|
||||
<div className="w-4 h-4 bg-red-600 flex items-center justify-center">
|
||||
<Code className="w-2 h-2 text-white" />
|
||||
</div>
|
||||
<span className="text-gray-300 text-xs font-medium">问题代码</span>
|
||||
<span className="text-green-400 text-xs font-bold font-mono uppercase">CODE_SNIPPET</span>
|
||||
</div>
|
||||
{issue.line_number && (
|
||||
<span className="text-gray-400 text-xs">第 {issue.line_number} 行</span>
|
||||
<span className="text-gray-400 text-xs font-mono">LINE: {issue.line_number}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-black/40 rounded p-2">
|
||||
<pre className="text-xs text-gray-100 overflow-x-auto">
|
||||
<div className="bg-black/40 p-2 border border-gray-700">
|
||||
<pre className="text-xs text-green-400 font-mono overflow-x-auto">
|
||||
<code>{issue.code_snippet}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{issue.suggestion && (
|
||||
<div className="bg-white border border-blue-200 rounded-lg p-3 shadow-sm">
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="w-5 h-5 bg-blue-600 rounded flex items-center justify-center mr-2">
|
||||
<Lightbulb className="w-3 h-3 text-white" />
|
||||
<div className="bg-blue-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center mb-2 border-b-2 border-blue-200 pb-1">
|
||||
<div className="w-5 h-5 bg-blue-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||
<Lightbulb className="w-3 h-3" />
|
||||
</div>
|
||||
<span className="font-medium text-blue-800 text-sm">修复建议</span>
|
||||
<span className="font-bold text-blue-800 text-sm uppercase font-display">修复建议</span>
|
||||
</div>
|
||||
<p className="text-blue-700 text-xs leading-relaxed">{issue.suggestion}</p>
|
||||
<p className="text-blue-900 text-xs leading-relaxed font-mono font-medium">{issue.suggestion}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -162,44 +160,44 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
|
||||
if (parsedExplanation) {
|
||||
return (
|
||||
<div className="bg-white border border-red-200 rounded-lg p-3 shadow-sm">
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="w-5 h-5 bg-red-600 rounded flex items-center justify-center mr-2">
|
||||
<Zap className="w-3 h-3 text-white" />
|
||||
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center mb-2 border-b-2 border-red-200 pb-1">
|
||||
<div className="w-5 h-5 bg-red-600 border-2 border-black flex items-center justify-center mr-2 text-white">
|
||||
<Zap className="w-3 h-3" />
|
||||
</div>
|
||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
||||
<span className="font-bold text-red-800 text-sm uppercase font-display">AI 解释</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="space-y-2 text-xs font-mono">
|
||||
{parsedExplanation.what && (
|
||||
<div className="border-l-2 border-red-600 pl-2">
|
||||
<span className="font-medium text-red-700">问题:</span>
|
||||
<span className="text-gray-700 ml-1">{parsedExplanation.what}</span>
|
||||
<div className="border-l-4 border-red-600 pl-2">
|
||||
<span className="font-bold text-red-700 uppercase">问题:</span>
|
||||
<span className="text-gray-800 ml-1">{parsedExplanation.what}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parsedExplanation.why && (
|
||||
<div className="border-l-2 border-gray-600 pl-2">
|
||||
<span className="font-medium text-gray-700">原因:</span>
|
||||
<span className="text-gray-700 ml-1">{parsedExplanation.why}</span>
|
||||
<div className="border-l-4 border-gray-600 pl-2">
|
||||
<span className="font-bold text-gray-700 uppercase">原因:</span>
|
||||
<span className="text-gray-800 ml-1">{parsedExplanation.why}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parsedExplanation.how && (
|
||||
<div className="border-l-2 border-black pl-2">
|
||||
<span className="font-medium text-black">方案:</span>
|
||||
<span className="text-gray-700 ml-1">{parsedExplanation.how}</span>
|
||||
<div className="border-l-4 border-black pl-2">
|
||||
<span className="font-bold text-black uppercase">方案:</span>
|
||||
<span className="text-gray-800 ml-1">{parsedExplanation.how}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{parsedExplanation.learn_more && (
|
||||
<div className="border-l-2 border-red-400 pl-2">
|
||||
<span className="font-medium text-red-600">链接:</span>
|
||||
<div className="border-l-4 border-blue-400 pl-2">
|
||||
<span className="font-bold text-blue-600 uppercase">链接:</span>
|
||||
<a
|
||||
href={parsedExplanation.learn_more}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-red-600 hover:text-red-800 hover:underline ml-1"
|
||||
className="text-blue-600 hover:text-blue-800 hover:underline ml-1 font-bold"
|
||||
>
|
||||
{parsedExplanation.learn_more}
|
||||
</a>
|
||||
|
|
@ -211,12 +209,12 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
} else {
|
||||
// 如果无法解析JSON,回退到原始显示方式
|
||||
return (
|
||||
<div className="bg-white border border-red-200 rounded-lg p-3">
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="bg-red-50 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<div className="flex items-center mb-2 border-b-2 border-red-200 pb-1">
|
||||
<Zap className="w-4 h-4 text-red-600 mr-2" />
|
||||
<span className="font-medium text-red-800 text-sm">AI 解释</span>
|
||||
<span className="font-bold text-red-800 text-sm uppercase font-display">AI 解释</span>
|
||||
</div>
|
||||
<p className="text-gray-700 text-xs leading-relaxed">{issue.ai_explanation}</p>
|
||||
<p className="text-gray-800 text-xs leading-relaxed font-mono">{issue.ai_explanation}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -227,14 +225,14 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
|
||||
if (issues.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-16">
|
||||
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<div className="text-center py-16 border-2 border-dashed border-black bg-green-50">
|
||||
<div className="w-20 h-20 bg-green-100 border-2 border-black flex items-center justify-center mx-auto mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<CheckCircle className="w-12 h-12 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-green-800 mb-3">代码质量优秀!</h3>
|
||||
<p className="text-green-600 text-lg mb-6">恭喜!没有发现任何问题</p>
|
||||
<div className="bg-green-50 rounded-lg p-6 max-w-md mx-auto">
|
||||
<p className="text-green-700 text-sm">
|
||||
<h3 className="text-2xl font-display font-bold text-green-800 mb-3 uppercase">代码质量优秀!</h3>
|
||||
<p className="text-green-700 text-lg mb-6 font-mono font-bold">恭喜!没有发现任何问题</p>
|
||||
<div className="bg-white border-2 border-black p-6 max-w-md mx-auto shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<p className="text-black text-sm font-mono">
|
||||
您的代码通过了所有质量检查,包括安全性、性能、可维护性等各个方面的评估。
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -244,72 +242,72 @@ function IssuesList({ issues }: { issues: AuditIssue[] }) {
|
|||
|
||||
return (
|
||||
<Tabs defaultValue="all" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-5 mb-6">
|
||||
<TabsTrigger value="all" className="text-sm">
|
||||
<TabsList className="grid w-full grid-cols-5 mb-6 bg-transparent border-2 border-black p-0 h-auto gap-0">
|
||||
<TabsTrigger value="all" className="rounded-none border-r-2 border-black data-[state=active]:bg-black data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
全部 ({issues.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="critical" className="text-sm">
|
||||
<TabsTrigger value="critical" className="rounded-none border-r-2 border-black data-[state=active]:bg-red-600 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
严重 ({criticalIssues.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="high" className="text-sm">
|
||||
<TabsTrigger value="high" className="rounded-none border-r-2 border-black data-[state=active]:bg-orange-500 data-[state=active]:text-white font-mono font-bold uppercase h-10 text-xs">
|
||||
高 ({highIssues.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="medium" className="text-sm">
|
||||
<TabsTrigger value="medium" className="rounded-none border-r-2 border-black data-[state=active]:bg-yellow-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
|
||||
中等 ({mediumIssues.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="low" className="text-sm">
|
||||
<TabsTrigger value="low" className="rounded-none data-[state=active]:bg-blue-400 data-[state=active]:text-black font-mono font-bold uppercase h-10 text-xs">
|
||||
低 ({lowIssues.length})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="all" className="space-y-4 mt-6">
|
||||
<TabsContent value="all" className="space-y-4 mt-0">
|
||||
{issues.map((issue, index) => renderIssue(issue, index))}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="critical" className="space-y-4 mt-6">
|
||||
<TabsContent value="critical" className="space-y-4 mt-0">
|
||||
{criticalIssues.length > 0 ? (
|
||||
criticalIssues.map((issue, index) => renderIssue(issue, index))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现严重问题</h3>
|
||||
<p className="text-gray-500">代码在严重级别的检查中表现良好</p>
|
||||
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现严重问题</h3>
|
||||
<p className="text-gray-500 font-mono">代码在严重级别的检查中表现良好</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="high" className="space-y-4 mt-6">
|
||||
<TabsContent value="high" className="space-y-4 mt-0">
|
||||
{highIssues.length > 0 ? (
|
||||
highIssues.map((issue, index) => renderIssue(issue, index))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现高优先级问题</h3>
|
||||
<p className="text-gray-500">代码在高优先级检查中表现良好</p>
|
||||
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现高优先级问题</h3>
|
||||
<p className="text-gray-500 font-mono">代码在高优先级检查中表现良好</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="medium" className="space-y-4 mt-6">
|
||||
<TabsContent value="medium" className="space-y-4 mt-0">
|
||||
{mediumIssues.length > 0 ? (
|
||||
mediumIssues.map((issue, index) => renderIssue(issue, index))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现中等优先级问题</h3>
|
||||
<p className="text-gray-500">代码在中等优先级检查中表现良好</p>
|
||||
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现中等优先级问题</h3>
|
||||
<p className="text-gray-500 font-mono">代码在中等优先级检查中表现良好</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="low" className="space-y-4 mt-6">
|
||||
<TabsContent value="low" className="space-y-4 mt-0">
|
||||
{lowIssues.length > 0 ? (
|
||||
lowIssues.map((issue, index) => renderIssue(issue, index))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-center py-12 border-2 border-dashed border-black bg-gray-50">
|
||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">没有发现低优先级问题</h3>
|
||||
<p className="text-gray-500">代码在低优先级检查中表现良好</p>
|
||||
<h3 className="text-lg font-bold text-black uppercase mb-2 font-mono">没有发现低优先级问题</h3>
|
||||
<p className="text-gray-500 font-mono">代码在低优先级检查中表现良好</p>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
|
@ -416,32 +414,32 @@ export default function TaskDetail() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary"></div>
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||
<div className="animate-spin rounded-none h-32 w-32 border-8 border-primary border-t-transparent"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<div className="space-y-6 animate-fade-in font-mono">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link to="/audit-tasks">
|
||||
<Button variant="outline" size="sm">
|
||||
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
返回任务列表
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<Card className="card-modern">
|
||||
<CardContent className="empty-state py-16">
|
||||
<div className="empty-icon">
|
||||
<AlertTriangle className="w-8 h-8 text-red-500" />
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="py-16 flex flex-col items-center justify-center text-center">
|
||||
<div className="w-20 h-20 bg-red-50 border-2 border-black flex items-center justify-center mb-6 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
|
||||
<AlertTriangle className="w-10 h-10 text-red-500" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">任务不存在</h3>
|
||||
<p className="text-gray-500">请检查任务ID是否正确</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<h3 className="text-xl font-bold text-black uppercase mb-2 font-display">任务不存在</h3>
|
||||
<p className="text-gray-500 font-mono">请检查任务ID是否正确</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -450,24 +448,24 @@ export default function TaskDetail() {
|
|||
const progressPercentage = calculateTaskProgress(task.scanned_files, task.total_files);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<div className="space-y-6 animate-fade-in font-mono">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link to="/audit-tasks">
|
||||
<Button variant="outline" size="sm">
|
||||
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
返回任务列表
|
||||
</Button>
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="page-title">任务详情</h1>
|
||||
<p className="page-subtitle">{task.project?.name || '未知项目'} - 审计任务</p>
|
||||
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">任务详情</h1>
|
||||
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">{task.project?.name || '未知项目'} - 审计任务</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<Badge className={getStatusColor(task.status)}>
|
||||
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 3 py - 1 text - sm ${getStatusColor(task.status)} `}>
|
||||
{getStatusIcon(task.status)}
|
||||
<span className="ml-2">
|
||||
{task.status === 'completed' ? '已完成' :
|
||||
|
|
@ -476,12 +474,12 @@ export default function TaskDetail() {
|
|||
task.status === 'cancelled' ? '已取消' : '等待中'}
|
||||
</span>
|
||||
</Badge>
|
||||
|
||||
|
||||
{/* 已完成的任务显示导出按钮 */}
|
||||
{task.status === 'completed' && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="btn-primary"
|
||||
<Button
|
||||
size="sm"
|
||||
className="retro-btn bg-primary text-white hover:bg-primary/90 h-10"
|
||||
onClick={() => setExportDialogOpen(true)}
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
|
|
@ -492,101 +490,93 @@ export default function TaskDetail() {
|
|||
</div>
|
||||
|
||||
{/* 任务概览 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">扫描进度</p>
|
||||
<p className="stat-value text-xl">{progressPercentage}%</p>
|
||||
<Progress value={progressPercentage} className="mt-2" />
|
||||
</div>
|
||||
<div className="stat-icon from-primary to-accent">
|
||||
<Activity className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 font-mono">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="w-full">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">扫描进度</p>
|
||||
<p className="text-3xl font-bold text-black mb-2">{progressPercentage}%</p>
|
||||
<Progress value={progressPercentage} className="h-2 border-2 border-black rounded-none bg-gray-200 [&>div]:bg-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-primary border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ml-4">
|
||||
<Activity className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">发现问题</p>
|
||||
<p className="stat-value text-xl text-orange-600">{task.issues_count}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-orange-500 to-orange-600">
|
||||
<Bug className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">发现问题</p>
|
||||
<p className="text-3xl font-bold text-orange-600">{task.issues_count}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-orange-500 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<Bug className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">质量评分</p>
|
||||
<p className="stat-value text-xl text-primary">{task.quality_score.toFixed(1)}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-emerald-500 to-emerald-600">
|
||||
<TrendingUp className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">质量评分</p>
|
||||
<p className="text-3xl font-bold text-green-600">{task.quality_score.toFixed(1)}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-green-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<TrendingUp className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="stat-card">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="stat-label">代码行数</p>
|
||||
<p className="stat-value text-xl">{task.total_lines.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="stat-icon from-purple-500 to-purple-600">
|
||||
<FileText className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-5 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">代码行数</p>
|
||||
<p className="text-3xl font-bold text-purple-600">{task.total_lines.toLocaleString()}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="w-10 h-10 bg-purple-600 border-2 border-black flex items-center justify-center text-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<FileText className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 任务信息 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="card-modern">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Shield className="w-5 h-5 text-primary" />
|
||||
<span>任务信息</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||
<Shield className="w-5 h-5 mr-2 text-primary" />
|
||||
任务信息
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">任务类型</p>
|
||||
<p className="text-base">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">任务类型</p>
|
||||
<p className="text-base font-bold">
|
||||
{task.task_type === 'repository' ? '仓库审计任务' : '即时分析任务'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">目标分支</p>
|
||||
<p className="text-base flex items-center">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">目标分支</p>
|
||||
<p className="text-base font-bold flex items-center">
|
||||
<GitBranch className="w-4 h-4 mr-1" />
|
||||
{task.branch_name || '默认分支'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">创建时间</p>
|
||||
<p className="text-base flex items-center">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">创建时间</p>
|
||||
<p className="text-base font-bold flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
{formatDate(task.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
{task.completed_at && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">完成时间</p>
|
||||
<p className="text-base flex items-center">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">完成时间</p>
|
||||
<p className="text-base font-bold flex items-center">
|
||||
<CheckCircle className="w-4 h-4 mr-1" />
|
||||
{formatDate(task.completed_at)}
|
||||
</p>
|
||||
|
|
@ -597,10 +587,10 @@ export default function TaskDetail() {
|
|||
{/* 排除模式 */}
|
||||
{task.exclude_patterns && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-2">排除模式</p>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-2">排除模式</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{JSON.parse(task.exclude_patterns).map((pattern: string) => (
|
||||
<Badge key={pattern} variant="outline" className="text-xs">
|
||||
<Badge key={pattern} variant="outline" className="text-xs rounded-none border-black bg-gray-100 font-mono">
|
||||
{pattern}
|
||||
</Badge>
|
||||
))}
|
||||
|
|
@ -611,51 +601,51 @@ export default function TaskDetail() {
|
|||
{/* 扫描配置 */}
|
||||
{task.scan_config && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-2">扫描配置</p>
|
||||
<div className="bg-gray-50 rounded-lg p-3">
|
||||
<pre className="text-xs text-gray-600">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-2">扫描配置</p>
|
||||
<div className="bg-gray-900 border-2 border-black p-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
|
||||
<pre className="text-xs text-green-400 font-mono">
|
||||
{JSON.stringify(JSON.parse(task.scan_config), null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Card className="card-modern">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<FileText className="w-5 h-5 text-primary" />
|
||||
<span>项目信息</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||
<FileText className="w-5 h-5 mr-2 text-primary" />
|
||||
项目信息
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 space-y-4 font-mono">
|
||||
{task.project ? (
|
||||
<>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">项目名称</p>
|
||||
<Link to={`/projects/${task.project.id}`} className="text-base text-primary hover:underline">
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">项目名称</p>
|
||||
<Link to={`/ projects / ${task.project.id} `} className="text-base font-bold text-primary hover:underline hover:text-primary/80">
|
||||
{task.project.name}
|
||||
</Link>
|
||||
</div>
|
||||
{task.project.description && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">项目描述</p>
|
||||
<p className="text-sm text-gray-600">{task.project.description}</p>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">项目描述</p>
|
||||
<p className="text-sm text-gray-800 font-medium">{task.project.description}</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">仓库类型</p>
|
||||
<p className="text-base">{task.project.repository_type?.toUpperCase() || 'OTHER'}</p>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-1">仓库类型</p>
|
||||
<p className="text-base font-bold">{task.project.repository_type?.toUpperCase() || 'OTHER'}</p>
|
||||
</div>
|
||||
{task.project.programming_languages && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-2">编程语言</p>
|
||||
<p className="text-xs font-bold text-gray-600 uppercase mb-2">编程语言</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{JSON.parse(task.project.programming_languages).map((lang: string) => (
|
||||
<Badge key={lang} variant="secondary" className="text-xs">
|
||||
<Badge key={lang} variant="secondary" className="text-xs rounded-none border-2 border-black bg-white text-black font-bold uppercase">
|
||||
{lang}
|
||||
</Badge>
|
||||
))}
|
||||
|
|
@ -664,26 +654,26 @@ export default function TaskDetail() {
|
|||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-gray-500">项目信息不可用</p>
|
||||
<p className="text-gray-500 font-bold">项目信息不可用</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 问题列表 */}
|
||||
{issues.length > 0 && (
|
||||
<Card className="card-modern">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Bug className="w-6 h-6 text-orange-600" />
|
||||
<span>发现的问题 ({issues.length})</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
|
||||
<div className="p-4 border-b-2 border-black bg-gray-50">
|
||||
<h3 className="text-lg font-display font-bold uppercase flex items-center">
|
||||
<Bug className="w-6 h-6 mr-2 text-orange-600" />
|
||||
发现的问题 ({issues.length})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<IssuesList issues={issues} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 导出报告对话框 */}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { apiClient } from "./serverClient";
|
||||
import type {
|
||||
Profile,
|
||||
Project,
|
||||
ProjectMember,
|
||||
AuditTask,
|
||||
AuditIssue,
|
||||
import type {
|
||||
Profile,
|
||||
Project,
|
||||
ProjectMember,
|
||||
AuditTask,
|
||||
AuditIssue,
|
||||
InstantAnalysis,
|
||||
CreateProjectForm,
|
||||
CreateAuditTaskForm,
|
||||
|
|
@ -14,7 +14,7 @@ import type {
|
|||
// Implement the same interface as the original localDatabase.ts but using backend API
|
||||
export const api = {
|
||||
// ==================== Profile 相关方法 ====================
|
||||
|
||||
|
||||
async getProfilesById(_id: string): Promise<Profile | null> {
|
||||
try {
|
||||
const res = await apiClient.get('/users/me');
|
||||
|
|
@ -184,7 +184,7 @@ export const api = {
|
|||
}
|
||||
},
|
||||
|
||||
async createInstantAnalysis(_analysis: InstantAnalysisForm & {
|
||||
async createInstantAnalysis(_analysis: InstantAnalysisForm & {
|
||||
user_id: string;
|
||||
analysis_result?: string;
|
||||
issues_count?: number;
|
||||
|
|
@ -205,6 +205,7 @@ export const api = {
|
|||
completed_tasks: number;
|
||||
total_issues: number;
|
||||
resolved_issues: number;
|
||||
avg_quality_score: number;
|
||||
}> {
|
||||
try {
|
||||
const res = await apiClient.get('/projects/stats');
|
||||
|
|
@ -216,7 +217,8 @@ export const api = {
|
|||
total_tasks: 0,
|
||||
completed_tasks: 0,
|
||||
total_issues: 0,
|
||||
resolved_issues: 0
|
||||
resolved_issues: 0,
|
||||
avg_quality_score: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
|
|
@ -294,7 +296,7 @@ export const api = {
|
|||
}> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
|
||||
const res = await apiClient.post('/database/import', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ export default {
|
|||
},
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
mono: ['"Space Mono"', '"Courier New"', 'monospace'],
|
||||
sans: ['"Inter"', 'system-ui', 'sans-serif'],
|
||||
display: ['"Orbitron"', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
borderColor: {
|
||||
border: 'hsl(var(--border))',
|
||||
},
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
|
|
@ -55,127 +57,62 @@ export default {
|
|||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
// 专业深红色调色板
|
||||
crimson: {
|
||||
50: '#fef2f2',
|
||||
100: '#fee2e2',
|
||||
200: '#fecaca',
|
||||
300: '#fca5a5',
|
||||
400: '#f87171',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
800: '#991b1b',
|
||||
900: '#7f1d1d',
|
||||
950: '#450a0a',
|
||||
},
|
||||
burgundy: {
|
||||
50: '#fdf2f8',
|
||||
100: '#fce7f3',
|
||||
200: '#fbcfe8',
|
||||
300: '#f9a8d4',
|
||||
400: '#f472b6',
|
||||
500: '#ec4899',
|
||||
600: '#db2777',
|
||||
700: '#be185d',
|
||||
800: '#9d174d',
|
||||
900: '#831843',
|
||||
950: '#500724',
|
||||
},
|
||||
success: 'hsl(var(--success))',
|
||||
warning: 'hsl(var(--warning))',
|
||||
info: 'hsl(var(--info))',
|
||||
sidebar: {
|
||||
DEFAULT: 'hsl(var(--sidebar-background))',
|
||||
foreground: 'hsl(var(--sidebar-foreground))',
|
||||
primary: 'hsl(var(--sidebar-primary))',
|
||||
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
|
||||
accent: 'hsl(var(--sidebar-accent))',
|
||||
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
|
||||
border: 'hsl(var(--sidebar-border))',
|
||||
ring: 'hsl(var(--sidebar-ring))',
|
||||
},
|
||||
// Cassette Futurism Palette
|
||||
retro: {
|
||||
orange: '#FF3D00',
|
||||
teal: '#00E5FF',
|
||||
pink: '#FF00FF',
|
||||
yellow: '#FFEA00',
|
||||
dark: '#1A1A1A',
|
||||
light: '#F5F7FA',
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
backgroundImage: {
|
||||
'gradient-primary': 'var(--gradient-primary)',
|
||||
'gradient-card': 'var(--gradient-card)',
|
||||
'gradient-background': 'var(--gradient-background)',
|
||||
none: '0',
|
||||
},
|
||||
boxShadow: {
|
||||
card: 'var(--shadow-card)',
|
||||
hover: 'var(--shadow-hover)',
|
||||
'retro': '4px 4px 0px 0px rgba(0,0,0,1)',
|
||||
'retro-hover': '2px 2px 0px 0px rgba(0,0,0,1)',
|
||||
'neon': '0 0 5px theme("colors.primary.DEFAULT"), 0 0 20px theme("colors.primary.DEFAULT")',
|
||||
},
|
||||
keyframes: {
|
||||
'accordion-down': {
|
||||
from: {
|
||||
height: '0',
|
||||
},
|
||||
to: {
|
||||
height: 'var(--radix-accordion-content-height)',
|
||||
},
|
||||
from: { height: '0' },
|
||||
to: { height: 'var(--radix-accordion-content-height)' },
|
||||
},
|
||||
'accordion-up': {
|
||||
from: {
|
||||
height: 'var(--radix-accordion-content-height)',
|
||||
},
|
||||
to: {
|
||||
height: '0',
|
||||
},
|
||||
from: { height: 'var(--radix-accordion-content-height)' },
|
||||
to: { height: '0' },
|
||||
},
|
||||
'fade-in': {
|
||||
from: {
|
||||
opacity: '0',
|
||||
transform: 'translateY(10px)',
|
||||
},
|
||||
to: {
|
||||
opacity: '1',
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
'glitch': {
|
||||
'0%, 100%': { transform: 'translate(0)' },
|
||||
'20%': { transform: 'translate(-2px, 2px)' },
|
||||
'40%': { transform: 'translate(-2px, -2px)' },
|
||||
'60%': { transform: 'translate(2px, 2px)' },
|
||||
'80%': { transform: 'translate(2px, -2px)' },
|
||||
},
|
||||
'slide-in': {
|
||||
from: {
|
||||
opacity: '0',
|
||||
transform: 'translateX(-20px)',
|
||||
},
|
||||
to: {
|
||||
opacity: '1',
|
||||
transform: 'translateX(0)',
|
||||
},
|
||||
'scanline': {
|
||||
'0%': { transform: 'translateY(-100%)' },
|
||||
'100%': { transform: 'translateY(100%)' },
|
||||
},
|
||||
'pulse-slow': {
|
||||
'0%, 100%': { opacity: '1' },
|
||||
'50%': { opacity: '0.5' },
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||
'fade-in': 'fade-in 0.5s ease-out',
|
||||
'slide-in': 'slide-in 0.5s ease-out',
|
||||
'glitch': 'glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite',
|
||||
'scanline': 'scanline 8s linear infinite',
|
||||
'pulse-slow': 'pulse-slow 3s ease-in-out infinite',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('tailwindcss-animate'),
|
||||
function ({ addUtilities }) {
|
||||
addUtilities(
|
||||
{
|
||||
'.border-t-solid': { 'border-top-style': 'solid' },
|
||||
'.border-r-solid': { 'border-right-style': 'solid' },
|
||||
'.border-b-solid': { 'border-bottom-style': 'solid' },
|
||||
'.border-l-solid': { 'border-left-style': 'solid' },
|
||||
'.border-t-dashed': { 'border-top-style': 'dashed' },
|
||||
'.border-r-dashed': { 'border-right-style': 'dashed' },
|
||||
'.border-b-dashed': { 'border-bottom-style': 'dashed' },
|
||||
'.border-l-dashed': { 'border-left-style': 'dashed' },
|
||||
'.border-t-dotted': { 'border-top-style': 'dotted' },
|
||||
'.border-r-dotted': { 'border-right-style': 'dotted' },
|
||||
'.border-b-dotted': { 'border-bottom-style': 'dotted' },
|
||||
'.border-l-dotted': { 'border-left-style': 'dotted' },
|
||||
},
|
||||
['responsive']
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
Loading…
Reference in New Issue