refactor(frontend): modernize UI components with retro-futuristic styling

- Replace Card, Alert, and Separator components with retro-styled div elements
- Update DatabaseManager with retro-card styling and border-based design
- Refactor health status display to use styled Badge components instead of text
- Remove unused icon imports (Server, FileText) from DatabaseManager
- Clean up inline comments and unnecessary whitespace throughout components
- Simplify error handling by removing redundant console.error statements
- Update Sidebar, SystemConfig, Account, AdminDashboard, and other pages with consistent retro styling
- Apply uppercase font styling and monospace typography to match retro-futuristic theme
- Consolidate component structure across all pages for visual consistency
- Improve code readability by removing excessive blank lines and comments
This commit is contained in:
lintsinghua 2025-11-28 16:16:29 +08:00
parent b733181663
commit 1fc0ecd14a
12 changed files with 224 additions and 819 deletions

View File

@ -5,21 +5,16 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { import {
Download, Download,
Upload, Upload,
Trash2, Trash2,
AlertCircle, AlertCircle,
CheckCircle2, CheckCircle2,
Server,
Activity, Activity,
RefreshCw, RefreshCw,
Database, Database,
FileText,
AlertTriangle, AlertTriangle,
Info Info
} from 'lucide-react'; } from 'lucide-react';
@ -60,7 +55,6 @@ export function DatabaseManager() {
has_config: boolean; has_config: boolean;
} | null>(null); } | null>(null);
// 加载健康检查和统计信息
useEffect(() => { useEffect(() => {
loadHealth(); loadHealth();
loadStats(); loadStats();
@ -90,21 +84,16 @@ export function DatabaseManager() {
} }
}; };
// 导出数据
const handleExport = async () => { const handleExport = async () => {
try { try {
setLoading(true); setLoading(true);
setMessage(null); setMessage(null);
const exportData = await api.exportDatabase(); const exportData = await api.exportDatabase();
// 构建完整的导出数据
const fullData = { const fullData = {
version: "1.0.0", version: "1.0.0",
export_date: exportData.export_date, export_date: exportData.export_date,
data: exportData.data data: exportData.data
}; };
const blob = new Blob([JSON.stringify(fullData, null, 2)], { type: 'application/json' }); const blob = new Blob([JSON.stringify(fullData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
@ -114,14 +103,10 @@ export function DatabaseManager() {
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
toast.success('数据导出成功!'); toast.success('数据导出成功!');
setMessage({ type: 'success', text: '数据导出成功!' }); setMessage({ type: 'success', text: '数据导出成功!' });
// 刷新统计信息
loadStats(); loadStats();
} catch (error: any) { } catch (error: any) {
console.error('导出失败:', error);
const errorMsg = error?.response?.data?.detail || error?.message || '数据导出失败,请重试'; const errorMsg = error?.response?.data?.detail || error?.message || '数据导出失败,请重试';
toast.error(errorMsg); toast.error(errorMsg);
setMessage({ type: 'error', text: errorMsg }); setMessage({ type: 'error', text: errorMsg });
@ -130,31 +115,23 @@ export function DatabaseManager() {
} }
}; };
// 导入数据
const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => { const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (!file) return; if (!file) return;
// 验证文件类型
if (!file.name.endsWith('.json')) { if (!file.name.endsWith('.json')) {
toast.error('请选择 JSON 格式的文件'); toast.error('请选择 JSON 格式的文件');
event.target.value = ''; event.target.value = '';
return; return;
} }
// 验证文件大小(最大 50MB
if (file.size > 50 * 1024 * 1024) { if (file.size > 50 * 1024 * 1024) {
toast.error('文件大小不能超过 50MB'); toast.error('文件大小不能超过 50MB');
event.target.value = ''; event.target.value = '';
return; return;
} }
try { try {
setLoading(true); setLoading(true);
setMessage(null); setMessage(null);
const result = await api.importDatabase(file); const result = await api.importDatabase(file);
const imported = result.imported; const imported = result.imported;
const summary = [ const summary = [
imported.projects > 0 && `${imported.projects} 个项目`, imported.projects > 0 && `${imported.projects} 个项目`,
@ -163,48 +140,33 @@ export function DatabaseManager() {
imported.analyses > 0 && `${imported.analyses} 条分析记录`, imported.analyses > 0 && `${imported.analyses} 条分析记录`,
imported.config > 0 && '用户配置', imported.config > 0 && '用户配置',
].filter(Boolean).join('、'); ].filter(Boolean).join('、');
toast.success(`数据导入成功!已导入:${summary}`); toast.success(`数据导入成功!已导入:${summary}`);
setMessage({ type: 'success', text: `数据导入成功!已导入:${summary}` }); setMessage({ type: 'success', text: `数据导入成功!已导入:${summary}` });
// 清空文件输入
event.target.value = ''; event.target.value = '';
// 刷新统计信息和健康检查
loadStats(); loadStats();
loadHealth(); loadHealth();
// 延迟刷新页面
setTimeout(() => window.location.reload(), 2000); setTimeout(() => window.location.reload(), 2000);
} catch (error: any) { } catch (error: any) {
console.error('导入失败:', error);
const errorMsg = error?.response?.data?.detail || error?.message || '数据导入失败,请检查文件格式'; const errorMsg = error?.response?.data?.detail || error?.message || '数据导入失败,请检查文件格式';
toast.error(errorMsg); toast.error(errorMsg);
setMessage({ type: 'error', text: errorMsg }); setMessage({ type: 'error', text: errorMsg });
// 清空文件输入
event.target.value = ''; event.target.value = '';
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
// 清空数据
const handleClear = async () => { const handleClear = async () => {
if (!window.confirm('⚠️ 警告:确定要清空所有数据吗?\n\n此操作将删除\n- 所有项目\n- 所有任务\n- 所有问题\n- 所有分析记录\n- 用户配置\n\n此操作不可恢复')) { if (!window.confirm('⚠️ 警告:确定要清空所有数据吗?\n\n此操作将删除\n- 所有项目\n- 所有任务\n- 所有问题\n- 所有分析记录\n- 用户配置\n\n此操作不可恢复')) {
return; return;
} }
// 二次确认
if (!window.confirm('请再次确认:您真的要清空所有数据吗?')) { if (!window.confirm('请再次确认:您真的要清空所有数据吗?')) {
return; return;
} }
try { try {
setLoading(true); setLoading(true);
setMessage(null); setMessage(null);
const result = await api.clearDatabase(); const result = await api.clearDatabase();
const deleted = result.deleted; const deleted = result.deleted;
const summary = [ const summary = [
deleted.projects > 0 && `${deleted.projects} 个项目`, deleted.projects > 0 && `${deleted.projects} 个项目`,
@ -213,18 +175,12 @@ export function DatabaseManager() {
deleted.analyses > 0 && `${deleted.analyses} 条分析记录`, deleted.analyses > 0 && `${deleted.analyses} 条分析记录`,
deleted.config > 0 && '用户配置', deleted.config > 0 && '用户配置',
].filter(Boolean).join('、'); ].filter(Boolean).join('、');
toast.success(`数据已清空!已删除:${summary}`); toast.success(`数据已清空!已删除:${summary}`);
setMessage({ type: 'success', text: `数据已清空!已删除:${summary}` }); setMessage({ type: 'success', text: `数据已清空!已删除:${summary}` });
// 刷新统计信息和健康检查
loadStats(); loadStats();
loadHealth(); loadHealth();
// 延迟刷新页面
setTimeout(() => window.location.reload(), 2000); setTimeout(() => window.location.reload(), 2000);
} catch (error: any) { } catch (error: any) {
console.error('清空失败:', error);
const errorMsg = error?.response?.data?.detail || error?.message || '清空数据失败,请重试'; const errorMsg = error?.response?.data?.detail || error?.message || '清空数据失败,请重试';
toast.error(errorMsg); toast.error(errorMsg);
setMessage({ type: 'error', text: errorMsg }); setMessage({ type: 'error', text: errorMsg });
@ -233,241 +189,206 @@ export function DatabaseManager() {
} }
}; };
const getHealthStatusColor = (status: string) => { const getHealthStatusBadge = (status: string) => {
switch (status) { switch (status) {
case 'healthy': case 'healthy':
return 'text-green-600 bg-green-50 border-green-200'; return <Badge className="rounded-none border-black bg-green-100 text-green-800 font-bold uppercase"></Badge>;
case 'warning': case 'warning':
return 'text-yellow-600 bg-yellow-50 border-yellow-200'; return <Badge className="rounded-none border-black bg-yellow-100 text-yellow-800 font-bold uppercase"></Badge>;
case 'error': case 'error':
return 'text-red-600 bg-red-50 border-red-200'; return <Badge className="rounded-none border-black bg-red-100 text-red-800 font-bold uppercase"></Badge>;
default: default:
return 'text-gray-600 bg-gray-50 border-gray-200'; return <Badge className="rounded-none border-black bg-gray-100 text-gray-800 font-bold uppercase"></Badge>;
}
};
const getHealthStatusText = (status: string) => {
switch (status) {
case 'healthy':
return '健康';
case 'warning':
return '警告';
case 'error':
return '错误';
default:
return '未知';
} }
}; };
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* 健康检查 */} {/* 健康检查 */}
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<CardHeader> <div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">
<div className="flex items-center justify-between"> <div>
<div className="flex items-center gap-2"> <h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<Activity className="h-5 w-5" /> <Activity className="h-5 w-5" />
<CardTitle></CardTitle>
</div> </h3>
<Button <p className="text-xs text-gray-500 font-mono mt-1"></p>
variant="outline"
size="sm"
onClick={loadHealth}
disabled={healthLoading}
>
<RefreshCw className={`h-4 w-4 mr-2 ${healthLoading ? 'animate-spin' : ''}`} />
</Button>
</div> </div>
<CardDescription> <Button
variant="outline"
</CardDescription> size="sm"
</CardHeader> onClick={loadHealth}
<CardContent className="space-y-4"> disabled={healthLoading}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-8 font-bold uppercase"
>
<RefreshCw className={`h-3 w-3 mr-2 ${healthLoading ? 'animate-spin' : ''}`} />
</Button>
</div>
<div className="p-6 font-mono">
{healthLoading ? ( {healthLoading ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" /> <RefreshCw className="h-6 w-6 animate-spin" />
</div> </div>
) : health ? ( ) : health ? (
<> <div className="space-y-4">
<div className={`flex items-center gap-3 p-4 rounded-lg border ${getHealthStatusColor(health.status)}`}> <div className="flex items-center gap-4">
{health.status === 'healthy' ? ( {health.status === 'healthy' ? (
<CheckCircle2 className="h-5 w-5" /> <CheckCircle2 className="h-5 w-5 text-green-600" />
) : health.status === 'warning' ? ( ) : health.status === 'warning' ? (
<AlertTriangle className="h-5 w-5" /> <AlertTriangle className="h-5 w-5 text-yellow-600" />
) : ( ) : (
<AlertCircle className="h-5 w-5" /> <AlertCircle className="h-5 w-5 text-red-600" />
)} )}
<div className="flex-1"> <div className="flex items-center gap-2">
<div className="flex items-center gap-2"> <span className="font-bold uppercase text-sm"></span>
<span className="font-semibold"></span> {getHealthStatusBadge(health.status)}
<Badge variant={health.status === 'healthy' ? 'default' : health.status === 'warning' ? 'secondary' : 'destructive'}>
{getHealthStatusText(health.status)}
</Badge>
</div>
<div className="text-sm mt-1">
{health.database_connected ? '正常' : '异常'} |
{health.total_records.toLocaleString()}
</div>
</div> </div>
<span className="text-sm">
{health.database_connected ? '正常' : '异常'} | {health.total_records.toLocaleString()}
</span>
</div> </div>
{health.issues.length > 0 && ( {health.issues.length > 0 && (
<Alert variant="destructive"> <div className="bg-red-50 border-2 border-red-500 p-4 shadow-[2px_2px_0px_0px_rgba(239,68,68,1)]">
<AlertCircle className="h-4 w-4" /> <p className="font-bold text-red-800 uppercase text-sm mb-2"></p>
<AlertDescription> <ul className="list-disc list-inside space-y-1 text-sm text-red-700">
<div className="font-semibold mb-2"></div> {health.issues.map((issue, index) => (
<ul className="list-disc list-inside space-y-1"> <li key={index}>{issue}</li>
{health.issues.map((issue, index) => ( ))}
<li key={index}>{issue}</li> </ul>
))} </div>
</ul>
</AlertDescription>
</Alert>
)} )}
{health.warnings.length > 0 && ( {health.warnings.length > 0 && (
<Alert> <div className="bg-yellow-50 border-2 border-yellow-500 p-4 shadow-[2px_2px_0px_0px_rgba(234,179,8,1)]">
<AlertTriangle className="h-4 w-4" /> <p className="font-bold text-yellow-800 uppercase text-sm mb-2"></p>
<AlertDescription> <ul className="list-disc list-inside space-y-1 text-sm text-yellow-700">
<div className="font-semibold mb-2"></div> {health.warnings.map((warning, index) => (
<ul className="list-disc list-inside space-y-1"> <li key={index}>{warning}</li>
{health.warnings.map((warning, index) => ( ))}
<li key={index}>{warning}</li> </ul>
))} </div>
</ul>
</AlertDescription>
</Alert>
)} )}
</>
) : (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription></AlertDescription>
</Alert>
)}
</CardContent>
</Card>
{/* 统计信息 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Database className="h-5 w-5" />
<CardTitle></CardTitle>
</div> </div>
<Button ) : (
variant="outline" <div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[2px_2px_0px_0px_rgba(59,130,246,1)]">
size="sm" <Info className="h-5 w-5 text-blue-600 mt-0.5" />
onClick={loadStats} <p className="text-sm text-blue-800 font-bold"></p>
disabled={statsLoading} </div>
> )}
<RefreshCw className={`h-4 w-4 mr-2 ${statsLoading ? 'animate-spin' : ''}`} /> </div>
</div>
</Button>
{/* 详细统计 */}
<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">
<div>
<h3 className="text-lg font-display font-bold uppercase flex items-center gap-2">
<Database className="h-5 w-5" />
</h3>
<p className="text-xs text-gray-500 font-mono mt-1"></p>
</div> </div>
<CardDescription> <Button
variant="outline"
</CardDescription> size="sm"
</CardHeader> onClick={loadStats}
<CardContent> disabled={statsLoading}
className="retro-btn bg-white text-black border-2 border-black hover:bg-gray-100 rounded-none h-8 font-bold uppercase"
>
<RefreshCw className={`h-3 w-3 mr-2 ${statsLoading ? 'animate-spin' : ''}`} />
</Button>
</div>
<div className="p-6 font-mono">
{statsLoading ? ( {statsLoading ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" /> <RefreshCw className="h-6 w-6 animate-spin" />
</div> </div>
) : stats ? ( ) : stats ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-1"> <div className="p-4 border-2 border-black bg-gray-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-sm text-muted-foreground"></div> <p className="text-xs text-gray-600 uppercase font-bold"></p>
<div className="text-2xl font-bold">{stats.total_projects}</div> <p className="text-2xl font-bold">{stats.total_projects}</p>
<div className="text-xs text-muted-foreground">: {stats.active_projects}</div> <p className="text-xs text-gray-500">: {stats.active_projects}</p>
</div> </div>
<div className="space-y-1"> <div className="p-4 border-2 border-black bg-gray-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-sm text-muted-foreground"></div> <p className="text-xs text-gray-600 uppercase font-bold"></p>
<div className="text-2xl font-bold">{stats.total_tasks}</div> <p className="text-2xl font-bold">{stats.total_tasks}</p>
<div className="text-xs text-muted-foreground"> <p className="text-xs text-gray-500">: {stats.completed_tasks} | : {stats.running_tasks}</p>
: {stats.completed_tasks} | : {stats.running_tasks}
</div>
</div> </div>
<div className="space-y-1"> <div className="p-4 border-2 border-black bg-gray-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-sm text-muted-foreground"></div> <p className="text-xs text-gray-600 uppercase font-bold"></p>
<div className="text-2xl font-bold">{stats.total_issues}</div> <p className="text-2xl font-bold">{stats.total_issues}</p>
<div className="text-xs text-muted-foreground"> <p className="text-xs text-gray-500">: {stats.open_issues} | : {stats.resolved_issues}</p>
: {stats.open_issues} | : {stats.resolved_issues}
</div>
</div> </div>
<div className="space-y-1"> <div className="p-4 border-2 border-black bg-gray-50 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div className="text-sm text-muted-foreground"></div> <p className="text-xs text-gray-600 uppercase font-bold"></p>
<div className="text-2xl font-bold">{stats.total_analyses}</div> <p className="text-2xl font-bold">{stats.total_analyses}</p>
<div className="text-xs text-muted-foreground"></div> <p className="text-xs text-gray-500"></p>
</div> </div>
</div> </div>
) : ( ) : (
<Alert> <div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[2px_2px_0px_0px_rgba(59,130,246,1)]">
<Info className="h-4 w-4" /> <Info className="h-5 w-5 text-blue-600 mt-0.5" />
<AlertDescription></AlertDescription> <p className="text-sm text-blue-800 font-bold"></p>
</Alert> </div>
)} )}
</CardContent> </div>
</Card> </div>
{/* 数据操作 */} {/* 数据操作 */}
<Card> <div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0">
<CardHeader> <div className="p-4 border-b-2 border-black bg-gray-50">
<CardTitle className="flex items-center gap-2"> <h3 className="text-lg font-display font-bold uppercase"></h3>
<Server className="h-5 w-5" /> <p className="text-xs text-gray-500 font-mono mt-1"></p>
</div>
</CardTitle> <div className="p-6 font-mono space-y-6">
<CardDescription>
PostgreSQL
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{message && ( {message && (
<Alert variant={message.type === 'error' ? 'destructive' : 'default'}> <div className={`p-4 border-2 flex items-start gap-3 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] ${
message.type === 'success'
? 'bg-green-50 border-green-500'
: 'bg-red-50 border-red-500'
}`}>
{message.type === 'success' ? ( {message.type === 'success' ? (
<CheckCircle2 className="h-4 w-4" /> <CheckCircle2 className="h-5 w-5 text-green-600 mt-0.5" />
) : ( ) : (
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-5 w-5 text-red-600 mt-0.5" />
)} )}
<AlertDescription>{message.text}</AlertDescription> <p className={`text-sm font-bold ${message.type === 'success' ? 'text-green-800' : 'text-red-800'}`}>
</Alert> {message.text}
</p>
</div>
)} )}
<div className="grid gap-4 md:grid-cols-3"> <div className="grid gap-6 md:grid-cols-3">
<div className="space-y-2"> <div className="space-y-3">
<h4 className="text-sm font-medium flex items-center gap-2"> <h4 className="text-sm font-bold uppercase flex items-center gap-2">
<Download className="h-4 w-4" /> <Download className="h-4 w-4" />
</h4> </h4>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-gray-500"> JSON </p>
JSON
</p>
<Button <Button
onClick={handleExport} onClick={handleExport}
disabled={loading} disabled={loading}
className="w-full" className="w-full 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)]"
variant="outline"
> >
<Download className="mr-2 h-4 w-4" /> <Download className="mr-2 h-4 w-4" />
</Button> </Button>
</div> </div>
<div className="space-y-2"> <div className="space-y-3">
<h4 className="text-sm font-medium flex items-center gap-2"> <h4 className="text-sm font-bold uppercase flex items-center gap-2">
<Upload className="h-4 w-4" /> <Upload className="h-4 w-4" />
</h4> </h4>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-gray-500"> JSON 50MB</p>
JSON 50MB
</p>
<Button <Button
onClick={() => document.getElementById('import-file')?.click()} onClick={() => document.getElementById('import-file')?.click()}
disabled={loading} disabled={loading}
className="w-full" className="w-full 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)]"
variant="outline"
> >
<Upload className="mr-2 h-4 w-4" /> <Upload className="mr-2 h-4 w-4" />
@ -481,19 +402,16 @@ export function DatabaseManager() {
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-3">
<h4 className="text-sm font-medium text-destructive flex items-center gap-2"> <h4 className="text-sm font-bold uppercase flex items-center gap-2 text-red-600">
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
</h4> </h4>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-gray-500"></p>
</p>
<Button <Button
onClick={handleClear} onClick={handleClear}
disabled={loading} disabled={loading}
className="w-full" className="w-full bg-red-500 hover:bg-red-600 text-white border-2 border-black rounded-none h-10 font-bold uppercase shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
variant="destructive"
> >
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
@ -501,19 +419,19 @@ export function DatabaseManager() {
</div> </div>
</div> </div>
<Separator /> <div className="pt-6 border-t-2 border-black border-dashed">
<div className="bg-blue-50 border-2 border-blue-500 p-4 flex items-start gap-3 shadow-[2px_2px_0px_0px_rgba(59,130,246,1)]">
<Alert> <Info className="h-5 w-5 text-blue-600 mt-0.5" />
<Info className="h-4 w-4" /> <p className="text-sm text-blue-800">
<AlertDescription> <strong className="uppercase"></strong>
<strong></strong> {dbMode === 'api'
{dbMode === 'api' ? '数据存储在后端 PostgreSQL 数据库中,支持多用户、多设备同步。建议定期导出备份。'
? '数据存储在后端 PostgreSQL 数据库中,支持多用户、多设备同步。建议定期导出备份。' : '建议定期导出数据备份,以防意外数据丢失。'}
: '建议定期导出数据备份,以防意外数据丢失。'} </p>
</AlertDescription> </div>
</Alert> </div>
</CardContent> </div>
</Card> </div>
</div> </div>
); );
} }

View File

@ -17,6 +17,7 @@ import {
UserCircle UserCircle
} from "lucide-react"; } from "lucide-react";
import routes from "@/app/routes"; import routes from "@/app/routes";
import { version } from "../../../package.json";
// Icon mapping for routes // Icon mapping for routes
const routeIcons: Record<string, React.ReactNode> = { const routeIcons: Record<string, React.ReactNode> = {
@ -192,7 +193,7 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
{!collapsed && ( {!collapsed && (
<div className="flex flex-col"> <div className="flex flex-col">
<span className="font-mono font-bold text-sm uppercase">GitHub</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> <span className="text-xs text-gray-500 font-mono">v{version}</span>
</div> </div>
)} )}
</a> </a>

View File

@ -371,7 +371,7 @@ export function SystemConfig() {
<Settings className="w-3 h-3 mr-2" /> <Settings className="w-3 h-3 mr-2" />
</TabsTrigger> </TabsTrigger>
<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"> <TabsTrigger value="other" 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">
<Globe className="w-3 h-3 mr-2" /> <Globe className="w-3 h-3 mr-2" />
</TabsTrigger> </TabsTrigger>

View File

@ -142,38 +142,10 @@ export default function Account() {
} }
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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 */}
<div className="relative z-10 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="text-3xl font-display font-bold text-black uppercase tracking-tighter">
<span className="text-primary">_管理</span>
</h1>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2"></p>
</div>
<div className="flex gap-3">
<Button
variant="outline"
onClick={handleSwitchAccount}
className="terminal-btn-primary bg-white text-black hover:bg-gray-100"
>
<UserPlus className="w-4 h-4 mr-2" />
</Button>
<Button
variant="destructive"
onClick={() => setShowLogoutDialog(true)}
className="bg-red-500 hover:bg-red-600 text-white border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
>
<LogOut className="w-4 h-4 mr-2" />
退
</Button>
</div>
</div>
<div className="relative z-10 grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="relative z-10 grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Profile Card */} {/* Profile Card */}
<Card className="retro-card border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]"> <Card className="retro-card border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
@ -201,6 +173,25 @@ export default function Account() {
<span className="font-bold">{formatDate(profile?.created_at)}</span> <span className="font-bold">{formatDate(profile?.created_at)}</span>
</div> </div>
</div> </div>
<Separator />
<div className="flex flex-col gap-2">
<Button
variant="outline"
onClick={handleSwitchAccount}
className="w-full terminal-btn-primary bg-white text-black hover:bg-gray-100"
>
<UserPlus className="w-4 h-4 mr-2" />
</Button>
<Button
variant="destructive"
onClick={() => setShowLogoutDialog(true)}
className="w-full bg-red-500 hover:bg-red-600 text-white border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]"
>
<LogOut className="w-4 h-4 mr-2" />
退
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -1,139 +1,18 @@
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Progress } from "@/components/ui/progress";
import {
HardDrive,
RefreshCw,
Info,
CheckCircle2,
AlertCircle,
FolderOpen,
Clock,
AlertTriangle,
TrendingUp,
Package,
Settings
} from "lucide-react";
import { api, dbMode } from "@/shared/config/database";
import { DatabaseManager } from "@/components/database/DatabaseManager"; import { DatabaseManager } from "@/components/database/DatabaseManager";
import { SystemConfig } from "@/components/system/SystemConfig"; import { SystemConfig } from "@/components/system/SystemConfig";
import { toast } from "sonner";
export default function AdminDashboard() { export default function AdminDashboard() {
const [stats, setStats] = useState({
totalProjects: 0,
activeProjects: 0,
totalTasks: 0,
completedTasks: 0,
totalIssues: 0,
resolvedIssues: 0,
storageUsed: '计算中...',
storageQuota: '未知'
});
const [loading, setLoading] = useState(true);
const [storageDetails, setStorageDetails] = useState<{
usage: number;
quota: number;
percentage: number;
} | null>(null);
useEffect(() => {
loadStats();
}, []);
const loadStats = async () => {
try {
setLoading(true);
const projectStats = await api.getProjectStats();
// 获取存储使用量IndexedDB
let storageUsed = '未知';
let storageQuota = '未知';
let details = null;
if ('storage' in navigator && 'estimate' in navigator.storage) {
try {
const estimate = await navigator.storage.estimate();
const usedMB = ((estimate.usage || 0) / 1024 / 1024).toFixed(2);
const quotaMB = ((estimate.quota || 0) / 1024 / 1024).toFixed(2);
const percentage = estimate.quota ? ((estimate.usage || 0) / estimate.quota * 100) : 0;
storageUsed = `${usedMB} MB`;
storageQuota = `${quotaMB} MB`;
details = {
usage: estimate.usage || 0,
quota: estimate.quota || 0,
percentage: Math.round(percentage)
};
} catch (e) {
console.error('Failed to estimate storage:', e);
}
}
setStats({
totalProjects: projectStats.total_projects || 0,
activeProjects: projectStats.active_projects || 0,
totalTasks: projectStats.total_tasks || 0,
completedTasks: projectStats.completed_tasks || 0,
totalIssues: projectStats.total_issues || 0,
resolvedIssues: projectStats.resolved_issues || 0,
storageUsed,
storageQuota
});
setStorageDetails(details);
} catch (error) {
console.error('Failed to load stats:', error);
toast.error("加载统计数据失败");
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen bg-background">
<div className="space-y-4 text-center">
<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>
);
}
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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="relative z-10 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-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 font-mono border-l-2 border-primary pl-2">
LLM设置使
</p>
</div>
<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>
</div>
{/* 主要内容标签页 */} {/* 主要内容标签页 */}
<Tabs defaultValue="config" className="w-full"> <Tabs defaultValue="config" className="w-full">
<TabsList className="grid w-full grid-cols-5 bg-transparent border-2 border-black p-0 h-auto gap-0 mb-6"> <TabsList className="grid w-full grid-cols-2 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="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="data" 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> </TabsList>
{/* 系统配置 */} {/* 系统配置 */}
@ -141,322 +20,10 @@ export default function AdminDashboard() {
<SystemConfig /> <SystemConfig />
</TabsContent> </TabsContent>
{/* 数据概览 */} {/* 数据管理 */}
<TabsContent value="overview" className="flex flex-col gap-6"> <TabsContent value="data" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 任务完成率 */}
<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" />
</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 font-bold">
<span></span>
<span>
{stats.totalTasks > 0
? Math.round((stats.completedTasks / stats.totalTasks) * 100)
: 0}%
</span>
</div>
<Progress
value={stats.totalTasks > 0
? (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 border-t-2 border-black border-dashed">
<div className="space-y-1">
<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-xs text-gray-500 uppercase font-bold"></p>
<p className="text-2xl font-bold text-green-600">{stats.completedTasks}</p>
</div>
</div>
</div>
</div>
{/* 问题解决率 */}
<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" />
</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 font-bold">
<span></span>
<span>
{stats.totalIssues > 0
? Math.round((stats.resolvedIssues / stats.totalIssues) * 100)
: 0}%
</span>
</div>
<Progress
value={stats.totalIssues > 0
? (stats.resolvedIssues / stats.totalIssues) * 100
: 0
}
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 border-t-2 border-black border-dashed">
<div className="space-y-1">
<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-xs text-gray-500 uppercase font-bold"></p>
<p className="text-2xl font-bold text-green-600">{stats.resolvedIssues}</p>
</div>
</div>
</div>
</div>
</div>
{/* 数据库表统计 */}
<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" />
</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-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-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-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-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-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-xs text-gray-600 uppercase font-bold"></p>
<p className="text-2xl font-bold">{stats.totalIssues}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</TabsContent>
{/* 存储管理 */}
<TabsContent value="storage" className="flex flex-col gap-6">
<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" />
使
</h3>
<p className="text-xs text-gray-500 font-mono mt-1">
IndexedDB 使
</p>
</div>
<div className="p-6 flex flex-col gap-6 font-mono">
{storageDetails ? (
<>
<div className="space-y-2">
<div className="flex items-center justify-between text-sm font-bold">
<span>使</span>
<span>{storageDetails.percentage}%</span>
</div>
<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-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-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-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>
</div>
</div>
{storageDetails.percentage > 80 && (
<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>
)}
</>
) : (
<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>
)}
</div>
</div>
<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-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-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-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-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-bold text-black uppercase text-sm">使</p>
<p className="text-xs text-gray-600 font-medium">
使
</p>
</div>
</div>
</div>
</div>
</TabsContent>
{/* 数据操作 */}
<TabsContent value="operations" className="flex flex-col gap-6">
<DatabaseManager /> <DatabaseManager />
</TabsContent> </TabsContent>
{/* 设置 */}
<TabsContent value="settings" className="flex flex-col gap-6">
<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-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div>
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-500 font-medium">
</p>
</div>
<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-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div>
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-500 font-medium">
</p>
</div>
<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-2 border-black bg-white shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
<div>
<p className="font-bold text-black uppercase text-sm"></p>
<p className="text-xs text-gray-500 font-medium">
</p>
</div>
<Badge variant="outline" className="rounded-none border-black bg-gray-100 font-mono text-xs"></Badge>
</div>
</div>
</div>
</div>
<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>
<ul className="list-disc list-inside space-y-2 ml-2">
<li></li>
<li>线访</li>
<li></li>
<li></li>
<li></li>
</ul>
<p className="pt-2 border-t-2 border-black border-dashed mt-4">
<strong className="text-black uppercase"></strong>
</p>
</div>
</div>
</TabsContent>
</Tabs> </Tabs>
</div> </div>
); );

View File

@ -128,22 +128,10 @@ export default function AuditTasks() {
} }
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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="relative z-10 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="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="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"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<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="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">
@ -207,6 +195,10 @@ export default function AuditTasks() {
className="pl-10 retro-input h-10" className="pl-10 retro-input h-10"
/> />
</div> </div>
<Button className="retro-btn bg-primary text-white hover:bg-primary/90 h-10 px-4 font-bold uppercase shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all" onClick={() => setShowCreateDialog(true)}>
<Plus className="w-4 h-4 mr-2" />
</Button>
<div className="flex space-x-2 w-full md:w-auto overflow-x-auto pb-2 md:pb-0"> <div className="flex space-x-2 w-full md:w-auto overflow-x-auto pb-2 md:pb-0">
<Button <Button
variant={statusFilter === "all" ? "default" : "outline"} variant={statusFilter === "all" ? "default" : "outline"}

View File

@ -11,7 +11,7 @@ import {
import { import {
Activity, AlertTriangle, Clock, Code, Activity, AlertTriangle, Clock, Code,
FileText, GitBranch, Shield, TrendingUp, Zap, FileText, GitBranch, Shield, TrendingUp, Zap,
BarChart3, Target, ArrowUpRight, Calendar, Terminal BarChart3, Target, ArrowUpRight, Calendar
} from "lucide-react"; } from "lucide-react";
import { api, dbMode, isDemoMode } from "@/shared/config/database"; import { api, dbMode, isDemoMode } from "@/shared/config/database";
import type { Project, AuditTask, ProjectStats } from "@/shared/types"; import type { Project, AuditTask, ProjectStats } from "@/shared/types";
@ -169,34 +169,6 @@ export default function Dashboard() {
{/* Decorative Background */} {/* 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" /> <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="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="retro-btn h-12">
<Zap className="w-4 h-4 mr-2" />
</Button>
</Link>
<Link to="/projects">
<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>
</Link>
</div>
</div>
{/* 数据库模式提示 */} {/* 数据库模式提示 */}
{isDemoMode && ( {isDemoMode && (
<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"> <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">
@ -337,15 +309,15 @@ export default function Dashboard() {
</div> </div>
<div> <div>
{issueTypeData.length > 0 ? ( {issueTypeData.length > 0 ? (
<ResponsiveContainer width="100%" height={250}> <ResponsiveContainer width="100%" height={280}>
<PieChart> <PieChart>
<Pie <Pie
data={issueTypeData} data={issueTypeData}
cx="50%" cx="50%"
cy="50%" cy="45%"
labelLine={true} labelLine={true}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`} label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
outerRadius={80} outerRadius={70}
fill="#8884d8" fill="#8884d8"
dataKey="value" dataKey="value"
stroke="#000" stroke="#000"
@ -367,7 +339,7 @@ export default function Dashboard() {
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
) : ( ) : (
<div className="flex items-center justify-center h-[250px] text-gray-400 border-2 border-dashed border-gray-300"> <div className="flex items-center justify-center h-[280px] text-gray-400 border-2 border-dashed border-gray-300">
<div className="text-center"> <div className="text-center">
<BarChart3 className="w-12 h-12 mx-auto mb-2 opacity-50" /> <BarChart3 className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p className="text-sm font-mono uppercase"></p> <p className="text-sm font-mono uppercase"></p>

View File

@ -535,16 +535,10 @@ class UserManager {
); );
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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="relative z-10 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>
{/* 代码输入区域 */} {/* 代码输入区域 */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-0"> <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"> <div className="p-4 border-b-2 border-black bg-gray-50 flex items-center justify-between">

View File

@ -313,28 +313,23 @@ export default function ProjectDetail() {
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-4 border-b-4 border-black pb-6 bg-white/50 backdrop-blur-sm p-4 retro-border"> <div className="relative z-10 flex items-center justify-between">
<div className="flex items-start space-x-4"> <div className="flex items-center space-x-4">
<Link to="/projects"> <Link to="/projects">
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10 w-10 p-0 flex items-center justify-center"> <Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10 w-10 p-0 flex items-center justify-center">
<ArrowLeft className="w-5 h-5" /> <ArrowLeft className="w-5 h-5" />
</Button> </Button>
</Link> </Link>
<div> <div className="flex items-center gap-2">
<div className="flex items-center gap-2"> <h1 className="text-2xl font-display font-bold text-black uppercase tracking-tighter">{project.name}</h1>
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">{project.name}</h1> <Badge variant="outline" className={`rounded-none border-2 border-black ${project.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}>
<Badge variant="outline" className={`rounded-none border-2 border-black ${project.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}> {project.is_active ? '活跃' : '暂停'}
{project.is_active ? '活跃' : '暂停'} </Badge>
</Badge>
</div>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2">
{project.description || '暂无项目描述'}
</p>
</div> </div>
</div> </div>
@ -350,7 +345,7 @@ export default function ProjectDetail() {
</div> </div>
</div> </div>
{/* ... (stats cards remain same) ... */} {/* 统计卡片 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{/* ... (stats cards content) ... */} {/* ... (stats cards content) ... */}
<div className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-4 hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] transition-all"> <div className="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">

View File

@ -16,7 +16,6 @@ import {
GitBranch, GitBranch,
Calendar, Calendar,
Users, Users,
Settings,
Code, Code,
Shield, Shield,
Activity, Activity,
@ -26,7 +25,9 @@ import {
Trash2, Trash2,
Edit, Edit,
CheckCircle, CheckCircle,
Terminal Terminal,
Github,
Folder
} from "lucide-react"; } from "lucide-react";
import { api } from "@/shared/config/database"; import { api } from "@/shared/config/database";
import { validateZipFile } from "@/features/projects/services"; import { validateZipFile } from "@/features/projects/services";
@ -262,9 +263,9 @@ export default function Projects() {
const getRepositoryIcon = (type?: string) => { const getRepositoryIcon = (type?: string) => {
switch (type) { switch (type) {
case 'github': return '🐙'; case 'github': return <Github className="w-5 h-5" />;
case 'gitlab': return '🦊'; case 'gitlab': return <GitBranch className="w-5 h-5 text-orange-500" />;
default: return '📁'; default: return <Folder className="w-5 h-5 text-gray-600" />;
} }
}; };
@ -375,21 +376,13 @@ export default function Projects() {
} }
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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 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"> <Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
<div> <DialogTrigger asChild className="hidden">
<h1 className="text-3xl font-display font-bold text-black uppercase tracking-tighter">
<span className="text-primary">_管理</span>
</h1>
<p className="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2"></p>
</div>
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
<DialogTrigger asChild>
<Button className="terminal-btn-primary h-12 text-lg"> <Button className="terminal-btn-primary h-12 text-lg">
<Plus className="w-5 h-5 mr-2" /> <Plus className="w-5 h-5 mr-2" />
@ -681,7 +674,6 @@ export default function Projects() {
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
{/* Stats Section */} {/* Stats Section */}
{projects.length > 0 && ( {projects.length > 0 && (
@ -739,17 +731,17 @@ export default function Projects() {
{/* Search and Filter */} {/* Search and Filter */}
<div className="retro-card p-4 flex items-center gap-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] relative z-10"> <div className="retro-card p-4 flex items-center gap-4 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] relative z-10">
<div className="flex-1 relative"> <div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-black w-4 h-4" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 w-4 h-4 z-10" />
<Input <Input
placeholder="搜索项目..." placeholder="搜索项目..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="terminal-input pl-10 w-full" className="bg-background border border-input pr-3 py-2 rounded-sm font-mono text-sm w-full pl-10"
/> />
</div> </div>
<Button variant="outline" className="terminal-btn-primary bg-white text-black hover:bg-gray-100"> <Button className="terminal-btn-primary h-10" onClick={() => setShowCreateDialog(true)}>
<Settings className="w-4 h-4 mr-2" /> <Plus className="w-4 h-4 mr-2" />
</Button> </Button>
</div> </div>
@ -760,7 +752,7 @@ export default function Projects() {
<div key={project.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:shadow-lg transition-all group flex flex-col h-full"> <div key={project.id} className="retro-card bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:shadow-lg transition-all group flex flex-col h-full">
<div className="p-4 border-b-2 border-black bg-gray-50 flex justify-between items-start"> <div className="p-4 border-b-2 border-black bg-gray-50 flex justify-between items-start">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="w-10 h-10 border border-border bg-white flex items-center justify-center text-2xl shadow-sm"> <div className="w-10 h-10 border-2 border-black bg-white flex items-center justify-center text-xl shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]">
{getRepositoryIcon(project.repository_type)} {getRepositoryIcon(project.repository_type)}
</div> </div>
<div> <div>

View File

@ -123,21 +123,10 @@ export default function RecycleBin() {
} }
return ( return (
<div className="flex flex-col gap-6 px-6 pt-0 pb-4 bg-background min-h-screen font-mono relative overflow-hidden"> <div className="flex flex-col gap-6 px-6 py-4 bg-background min-h-screen font-mono relative overflow-hidden">
{/* Decorative Background */} {/* 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" /> <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="relative z-10 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="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="text-gray-600 mt-1 font-mono border-l-2 border-primary pl-2"></p>
</div>
</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="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 items-center space-x-4">

View File

@ -448,24 +448,18 @@ export default function TaskDetail() {
const progressPercentage = calculateTaskProgress(task.scanned_files, task.total_files); const progressPercentage = calculateTaskProgress(task.scanned_files, task.total_files);
return ( return (
<div className="flex flex-col gap-6 animate-fade-in font-mono"> <div className="flex flex-col gap-6 px-6 py-4 animate-fade-in font-mono">
{/* 页面标题 */} {/* 顶部操作栏 */}
<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 justify-between">
<div className="flex items-center space-x-4"> <Link to="/audit-tasks">
<Link to="/audit-tasks"> <Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10">
<Button variant="outline" size="sm" className="retro-btn bg-white text-black hover:bg-gray-100 h-10"> <ArrowLeft className="w-4 h-4 mr-2" />
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
</Button> </Link>
</Link>
<div>
<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"> <div className="flex items-center space-x-3">
<Badge className={`rounded - none border - 2 border - black font - bold uppercase px - 3 py - 1 text - sm ${getStatusColor(task.status)} `}> <Badge className={`rounded-none border-2 border-black font-bold uppercase px-3 py-1 text-sm ${getStatusColor(task.status)}`}>
{getStatusIcon(task.status)} {getStatusIcon(task.status)}
<span className="ml-2"> <span className="ml-2">
{task.status === 'completed' ? '已完成' : {task.status === 'completed' ? '已完成' :