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:
3
app/value-model/assessment/loading.tsx
Normal file
3
app/value-model/assessment/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
320
app/value-model/assessment/page.tsx
Normal file
320
app/value-model/assessment/page.tsx
Normal file
@@ -0,0 +1,320 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import {
|
||||
BarChart3,
|
||||
Plus,
|
||||
Play,
|
||||
Search,
|
||||
Users,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
RefreshCw,
|
||||
Download,
|
||||
Filter,
|
||||
} from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { CreateAssessmentTaskDialog } from "@/components/dialogs/create-assessment-task-dialog"
|
||||
|
||||
interface AssessmentTask {
|
||||
id: string
|
||||
name: string
|
||||
model: string
|
||||
targetCrowd: string
|
||||
userCount: number
|
||||
status: "running" | "completed" | "failed" | "scheduled"
|
||||
progress: number
|
||||
createdAt: string
|
||||
completedAt: string | null
|
||||
schedule: string
|
||||
resultSummary?: {
|
||||
avgScore: number
|
||||
highValue: number
|
||||
mediumValue: number
|
||||
lowValue: number
|
||||
}
|
||||
}
|
||||
|
||||
const MOCK_TASKS: AssessmentTask[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "全量用户CLV评估",
|
||||
model: "CLV预测模型 v2.3.1",
|
||||
targetCrowd: "全部用户",
|
||||
userCount: 4028567890,
|
||||
status: "completed",
|
||||
progress: 100,
|
||||
createdAt: "2025-12-12 00:00:00",
|
||||
completedAt: "2025-12-12 03:45:00",
|
||||
schedule: "每周一",
|
||||
resultSummary: {
|
||||
avgScore: 65.8,
|
||||
highValue: 15.2,
|
||||
mediumValue: 45.6,
|
||||
lowValue: 39.2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "活跃用户RFM评分",
|
||||
model: "RFM评分模型 v1.5.0",
|
||||
targetCrowd: "近30天活跃用户",
|
||||
userCount: 1256789012,
|
||||
status: "running",
|
||||
progress: 67,
|
||||
createdAt: "2025-12-12 10:00:00",
|
||||
completedAt: null,
|
||||
schedule: "每天",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "流失风险预警",
|
||||
model: "流失预警模型 v3.1.2",
|
||||
targetCrowd: "沉默用户",
|
||||
userCount: 456789012,
|
||||
status: "completed",
|
||||
progress: 100,
|
||||
createdAt: "2025-12-11 22:00:00",
|
||||
completedAt: "2025-12-12 00:30:00",
|
||||
schedule: "每天",
|
||||
resultSummary: {
|
||||
avgScore: 0.45,
|
||||
highValue: 8.5,
|
||||
mediumValue: 25.3,
|
||||
lowValue: 66.2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "高价值用户细分",
|
||||
model: "用户分群模型 v1.0.0",
|
||||
targetCrowd: "价值评分>80用户",
|
||||
userCount: 125678901,
|
||||
status: "scheduled",
|
||||
progress: 0,
|
||||
createdAt: "2025-12-12 14:00:00",
|
||||
completedAt: null,
|
||||
schedule: "每周",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "欺诈风险扫描",
|
||||
model: "欺诈检测模型 v2.0.0",
|
||||
targetCrowd: "近7天交易用户",
|
||||
userCount: 89012345,
|
||||
status: "failed",
|
||||
progress: 45,
|
||||
createdAt: "2025-12-12 08:00:00",
|
||||
completedAt: null,
|
||||
schedule: "实时",
|
||||
},
|
||||
]
|
||||
|
||||
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 },
|
||||
scheduled: { label: "已排期", color: "bg-yellow-100 text-yellow-700", icon: Clock },
|
||||
}
|
||||
|
||||
export default function AssessmentServicePage() {
|
||||
const [tasks, setTasks] = useState<AssessmentTask[]>(MOCK_TASKS)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [statusFilter, setStatusFilter] = useState("all")
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
if (num >= 1000000000) return `${(num / 1000000000).toFixed(2)}B`
|
||||
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
|
||||
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
const filteredTasks = tasks.filter((task) => {
|
||||
const matchesSearch = task.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const matchesStatus = statusFilter === "all" || task.status === statusFilter
|
||||
return matchesSearch && matchesStatus
|
||||
})
|
||||
|
||||
const stats = {
|
||||
total: tasks.length,
|
||||
running: tasks.filter((t) => t.status === "running").length,
|
||||
completed: tasks.filter((t) => t.status === "completed").length,
|
||||
totalEvaluated: tasks.filter((t) => t.status === "completed").reduce((sum, t) => sum + t.userCount, 0),
|
||||
}
|
||||
|
||||
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>
|
||||
<Button
|
||||
className="bg-gradient-to-r from-blue-500 to-purple-500 text-white"
|
||||
onClick={() => setShowCreateDialog(true)}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
创建评估任务
|
||||
</Button>
|
||||
</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">
|
||||
<BarChart3 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">
|
||||
<Users className="w-4 h-4" />
|
||||
已评估用户
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900">{formatNumber(stats.totalEvaluated)}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="搜索任务..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 bg-white/60"
|
||||
/>
|
||||
</div>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-full md:w-40 bg-white/60">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
<SelectValue placeholder="状态筛选" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="running">运行中</SelectItem>
|
||||
<SelectItem value="completed">已完成</SelectItem>
|
||||
<SelectItem value="failed">失败</SelectItem>
|
||||
<SelectItem value="scheduled">已排期</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Task List */}
|
||||
<div className="space-y-4">
|
||||
{filteredTasks.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 mb-4">
|
||||
<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={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>
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-500">
|
||||
<span>模型: {task.model}</span>
|
||||
<span>目标: {task.targetCrowd}</span>
|
||||
<span>用户数: {formatNumber(task.userCount)}</span>
|
||||
<span>周期: {task.schedule}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{task.status === "completed" && (
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="w-4 h-4 mr-1" />
|
||||
导出
|
||||
</Button>
|
||||
)}
|
||||
{(task.status === "scheduled" || task.status === "failed") && (
|
||||
<Button variant="outline" size="sm">
|
||||
<Play className="w-4 h-4 mr-1" />
|
||||
执行
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{task.status === "running" && (
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between text-sm text-gray-500 mb-1">
|
||||
<span>执行进度</span>
|
||||
<span>{task.progress}%</span>
|
||||
</div>
|
||||
<Progress value={task.progress} className="h-2" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{task.resultSummary && (
|
||||
<div className="grid grid-cols-4 gap-4 p-4 rounded-xl bg-gray-50">
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-gray-900">{task.resultSummary.avgScore}</div>
|
||||
<div className="text-xs text-gray-500">平均分</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-green-600">{task.resultSummary.highValue}%</div>
|
||||
<div className="text-xs text-gray-500">高价值</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-yellow-600">{task.resultSummary.mediumValue}%</div>
|
||||
<div className="text-xs text-gray-500">中价值</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold text-gray-600">{task.resultSummary.lowValue}%</div>
|
||||
<div className="text-xs text-gray-500">低价值</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between text-xs text-gray-500 mt-4 pt-4 border-t border-gray-100">
|
||||
<span>创建时间: {task.createdAt}</span>
|
||||
{task.completedAt && <span>完成时间: {task.completedAt}</span>}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Create Task Dialog */}
|
||||
<CreateAssessmentTaskDialog open={showCreateDialog} onOpenChange={setShowCreateDialog} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user