refactor: restructure navigation and module layout

Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2026-01-31 04:32:36 +00:00
parent 22e725887a
commit b17b488f8e
105 changed files with 20530 additions and 3622 deletions

View File

@@ -0,0 +1,314 @@
"use client"
import { useState, useEffect } from "react"
import { Clock, Play, Pause, CheckCircle, XCircle, RefreshCw, Calendar, Settings, MoreHorizontal } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { ScheduleCalendarDialog } from "@/components/dialogs/schedule-calendar-dialog"
import { TaskDetailDialog } from "@/components/dialogs/task-detail-dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
interface ScheduledTask {
id: string
name: string
type: "sync" | "cleaning" | "tagging" | "model"
schedule: string
status: "running" | "completed" | "failed" | "waiting" | "paused"
progress: number
nextRun: string
lastRun: string
duration: string
logs: string[]
}
const MOCK_TASKS: ScheduledTask[] = [
{
id: "1",
name: "用户数据同步",
type: "sync",
schedule: "*/5 * * * *",
status: "running",
progress: 67,
nextRun: "-",
lastRun: "2025-12-12 14:30:00",
duration: "2分30秒",
logs: ["正在同步数据...", "已处理 67%"],
},
{
id: "2",
name: "交易数据清洗",
type: "cleaning",
schedule: "0 */1 * * *",
status: "completed",
progress: 100,
nextRun: "2025-12-12 15:00:00",
lastRun: "2025-12-12 14:00:00",
duration: "5分12秒",
logs: ["清洗完成,处理 89234567 条记录"],
},
{
id: "3",
name: "用户标签计算",
type: "tagging",
schedule: "0 2 * * *",
status: "waiting",
progress: 0,
nextRun: "2025-12-13 02:00:00",
lastRun: "2025-12-12 02:00:00",
duration: "15分38秒",
logs: ["等待执行"],
},
{
id: "4",
name: "CLV模型训练",
type: "model",
schedule: "0 3 * * 0",
status: "failed",
progress: 45,
nextRun: "2025-12-15 03:00:00",
lastRun: "2025-12-08 03:00:00",
duration: "-",
logs: ["错误:内存不足", "任务中断于 45%"],
},
{
id: "5",
name: "行为数据同步",
type: "sync",
schedule: "实时",
status: "running",
progress: 100,
nextRun: "-",
lastRun: "2025-12-12 14:35:12",
duration: "持续运行",
logs: ["Kafka消费正常", "当前lag: 125"],
},
]
const TYPE_CONFIG = {
sync: { label: "数据同步", color: "bg-blue-100 text-blue-700" },
cleaning: { label: "数据清洗", color: "bg-green-100 text-green-700" },
tagging: { label: "标签计算", color: "bg-purple-100 text-purple-700" },
model: { label: "模型训练", color: "bg-orange-100 text-orange-700" },
}
const STATUS_CONFIG = {
running: { label: "运行中", color: "bg-blue-100 text-blue-700", icon: RefreshCw },
completed: { label: "已完成", color: "bg-green-100 text-green-700", icon: CheckCircle },
failed: { label: "失败", color: "bg-red-100 text-red-700", icon: XCircle },
waiting: { label: "等待中", color: "bg-gray-100 text-gray-700", icon: Clock },
paused: { label: "已暂停", color: "bg-yellow-100 text-yellow-700", icon: Pause },
}
export default function TaskSchedulePage() {
const [tasks, setTasks] = useState<ScheduledTask[]>(MOCK_TASKS)
const [currentTime, setCurrentTime] = useState(new Date())
const [showCalendarDialog, setShowCalendarDialog] = useState(false)
const [showDetailDialog, setShowDetailDialog] = useState(false)
const [selectedTask, setSelectedTask] = useState<ScheduledTask | null>(null)
useEffect(() => {
const interval = setInterval(() => {
setCurrentTime(new Date())
// 模拟进度更新
setTasks((prev) =>
prev.map((task) => {
if (task.status === "running" && task.progress < 100) {
return { ...task, progress: Math.min(task.progress + Math.random() * 2, 100) }
}
return task
}),
)
}, 2000)
return () => clearInterval(interval)
}, [])
const stats = {
total: tasks.length,
running: tasks.filter((t) => t.status === "running").length,
completed: tasks.filter((t) => t.status === "completed").length,
failed: tasks.filter((t) => t.status === "failed").length,
}
const handleViewDetail = (task: ScheduledTask) => {
setSelectedTask(task)
setShowDetailDialog(true)
}
return (
<div className="space-y-6 p-6">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-gray-900"></h1>
<p className="text-gray-500 mt-1"></p>
</div>
<div className="flex items-center gap-3">
<Badge variant="outline" className="px-3 py-1">
<Clock className="w-3 h-3 mr-1" />
{currentTime.toLocaleTimeString()}
</Badge>
<Button variant="outline" onClick={() => setShowCalendarDialog(true)}>
<Calendar className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
<Settings className="w-4 h-4" />
</div>
<div className="text-2xl font-bold text-gray-900">{stats.total}</div>
</CardContent>
</Card>
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
<RefreshCw className="w-4 h-4 text-blue-500" />
</div>
<div className="text-2xl font-bold text-blue-600">{stats.running}</div>
</CardContent>
</Card>
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
<CheckCircle className="w-4 h-4 text-green-500" />
</div>
<div className="text-2xl font-bold text-green-600">{stats.completed}</div>
</CardContent>
</Card>
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center gap-2 text-gray-500 text-sm mb-1">
<XCircle className="w-4 h-4 text-red-500" />
</div>
<div className="text-2xl font-bold text-red-600">{stats.failed}</div>
</CardContent>
</Card>
</div>
{/* Task List */}
<div className="space-y-4">
{tasks.map((task) => {
const StatusIcon = STATUS_CONFIG[task.status].icon
return (
<Card key={task.id} className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-5">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="font-semibold text-gray-900">{task.name}</h3>
<Badge className={TYPE_CONFIG[task.type].color}>{TYPE_CONFIG[task.type].label}</Badge>
<Badge className={STATUS_CONFIG[task.status].color}>
<StatusIcon className={`w-3 h-3 mr-1 ${task.status === "running" ? "animate-spin" : ""}`} />
{STATUS_CONFIG[task.status].label}
</Badge>
</div>
{task.status === "running" && (
<div className="mb-3">
<div className="flex items-center justify-between text-sm text-gray-500 mb-1">
<span></span>
<span>{Math.round(task.progress)}%</span>
</div>
<Progress value={task.progress} className="h-2" />
</div>
)}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<span className="text-gray-500">:</span>
<code className="ml-1 text-xs bg-gray-100 px-1 rounded">{task.schedule}</code>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-1 text-gray-900">{task.nextRun}</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-1 text-gray-900">{task.lastRun}</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-1 text-gray-900">{task.duration}</span>
</div>
</div>
{task.logs.length > 0 && (
<div className="mt-3 p-2 bg-gray-50 rounded-lg">
<div className="text-xs text-gray-500 mb-1">:</div>
{task.logs.slice(-2).map((log, i) => (
<div key={i} className="text-xs text-gray-600">
{log}
</div>
))}
</div>
)}
</div>
<div className="flex items-center gap-2 ml-4">
{task.status === "running" ? (
<Button variant="outline" size="sm">
<Pause className="w-4 h-4" />
</Button>
) : (
<Button variant="outline" size="sm">
<Play className="w-4 h-4" />
</Button>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleViewDetail(task)}></DropdownMenuItem>
<DropdownMenuItem></DropdownMenuItem>
<DropdownMenuItem></DropdownMenuItem>
<DropdownMenuItem className="text-red-600"></DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</CardContent>
</Card>
)
})}
</div>
{/* Schedule Calendar Dialog */}
<ScheduleCalendarDialog open={showCalendarDialog} onOpenChange={setShowCalendarDialog} />
<TaskDetailDialog
open={showDetailDialog}
onOpenChange={setShowDetailDialog}
task={
selectedTask
? {
id: selectedTask.id,
name: selectedTask.name,
type: selectedTask.type,
status: selectedTask.status,
progress: selectedTask.progress,
schedule: selectedTask.schedule,
lastRun: selectedTask.lastRun,
nextRun: selectedTask.nextRun,
duration: selectedTask.duration,
}
: undefined
}
/>
</div>
)
}