feat: refactor data asset center for enhanced search and analytics
Refactor homepage for focused search and data display; streamline data platform; enhance user and tag management; focus AI assistant on data analysis and report generation. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
31
app/api/ai-query/route.ts
Normal file
31
app/api/ai-query/route.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { getMindsDBConnector } from "@/lib/mindsdb-connector"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { query, model = "gpt4", parameters = {}, useCache = true } = body
|
||||
|
||||
if (!query || !query.trim()) {
|
||||
return NextResponse.json({ error: "AI查询不能为空" }, { status: 400 })
|
||||
}
|
||||
|
||||
const mindsDB = getMindsDBConnector()
|
||||
|
||||
const result = await mindsDB.aiQuery({
|
||||
query,
|
||||
model,
|
||||
parameters,
|
||||
useCache,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
result,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("AI查询API错误:", error)
|
||||
return NextResponse.json({ error: "AI查询失败,请稍后重试" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
105
app/api/ingest/route.ts
Normal file
105
app/api/ingest/route.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { IngestionService, type IngestionRequest } from "@/services/IngestionService"
|
||||
|
||||
// POST /api/ingest - 数据接入端点
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
// 验证请求数据
|
||||
if (!body.source || !body.originalData) {
|
||||
return NextResponse.json({ error: "缺少必需字段: source 和 originalData" }, { status: 400 })
|
||||
}
|
||||
|
||||
const ingestionRequest: IngestionRequest = {
|
||||
source: body.source,
|
||||
sourceUserId: body.sourceUserId,
|
||||
sourceRecordId: body.sourceRecordId,
|
||||
originalData: body.originalData,
|
||||
timestamp: body.timestamp || new Date().toISOString(),
|
||||
}
|
||||
|
||||
const ingestionService = IngestionService.getInstance()
|
||||
const result = await ingestionService.processIngestionRequest(ingestionRequest)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
userId: result.userId,
|
||||
coreProfileFields: Object.keys(result.coreProfile).length,
|
||||
tagsCount: result.unifiedTags.length,
|
||||
sourceProfilesCount: result.sourceProfiles.length,
|
||||
},
|
||||
message: "数据接入成功",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("数据接入API错误:", error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "数据接入失败",
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/ingest/batch - 批量数据接入端点
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
if (!Array.isArray(body.requests)) {
|
||||
return NextResponse.json({ error: "requests 必须是数组" }, { status: 400 })
|
||||
}
|
||||
|
||||
const ingestionService = IngestionService.getInstance()
|
||||
const results = await ingestionService.processBatchIngestion(body.requests)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
processedCount: results.length,
|
||||
totalRequests: body.requests.length,
|
||||
results: results.map((result) => ({
|
||||
userId: result.userId,
|
||||
coreProfileFields: Object.keys(result.coreProfile).length,
|
||||
tagsCount: result.unifiedTags.length,
|
||||
})),
|
||||
},
|
||||
message: `批量处理完成,成功处理 ${results.length}/${body.requests.length} 条记录`,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("批量数据接入API错误:", error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "批量数据接入失败",
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/ingest/status - 获取数据接入状态
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// 模拟获取接入状态数据
|
||||
const status = {
|
||||
totalIngested: 125678,
|
||||
todayIngested: 1234,
|
||||
activeSources: 8,
|
||||
lastIngestionTime: new Date().toISOString(),
|
||||
dataQuality: 94.6,
|
||||
processingQueue: 23,
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: status,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("获取接入状态API错误:", error)
|
||||
return NextResponse.json({ error: "获取状态失败" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
65
app/api/search/route.ts
Normal file
65
app/api/search/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { getIntelligentSearchService } from "@/services/intelligent-search-service"
|
||||
import { getMindsDBConnector } from "@/lib/mindsdb-connector"
|
||||
|
||||
// 初始化MindsDB连接
|
||||
const mindsDBConfig = {
|
||||
host: process.env.MINDSDB_HOST || "localhost",
|
||||
port: Number.parseInt(process.env.MINDSDB_PORT || "47334"),
|
||||
username: process.env.MINDSDB_USERNAME || "mindsdb",
|
||||
password: process.env.MINDSDB_PASSWORD || "",
|
||||
database: process.env.MINDSDB_DATABASE || "mindsdb",
|
||||
}
|
||||
|
||||
// 初始化连接器
|
||||
getMindsDBConnector(mindsDBConfig)
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const query = searchParams.get("q") || ""
|
||||
const type = (searchParams.get("type") as "user" | "traffic" | "all") || "all"
|
||||
const limit = Number.parseInt(searchParams.get("limit") || "50")
|
||||
const offset = Number.parseInt(searchParams.get("offset") || "0")
|
||||
const useAI = searchParams.get("ai") === "true"
|
||||
const includeInsights = searchParams.get("insights") === "true"
|
||||
|
||||
if (!query.trim()) {
|
||||
return NextResponse.json({ error: "搜索查询不能为空" }, { status: 400 })
|
||||
}
|
||||
|
||||
const searchService = getIntelligentSearchService()
|
||||
|
||||
const results = await searchService.search(query, type, {
|
||||
limit,
|
||||
offset,
|
||||
useAI,
|
||||
includeInsights,
|
||||
filters: {},
|
||||
})
|
||||
|
||||
return NextResponse.json(results)
|
||||
} catch (error) {
|
||||
console.error("搜索API错误:", error)
|
||||
return NextResponse.json({ error: "搜索失败,请稍后重试" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { query, type = "all", options = {} } = body
|
||||
|
||||
if (!query || !query.trim()) {
|
||||
return NextResponse.json({ error: "搜索查询不能为空" }, { status: 400 })
|
||||
}
|
||||
|
||||
const searchService = getIntelligentSearchService()
|
||||
const results = await searchService.search(query, type, options)
|
||||
|
||||
return NextResponse.json(results)
|
||||
} catch (error) {
|
||||
console.error("搜索API错误:", error)
|
||||
return NextResponse.json({ error: "搜索失败,请稍后重试" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
24
app/api/system-status/route.ts
Normal file
24
app/api/system-status/route.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { getMindsDBConnector } from "@/lib/mindsdb-connector"
|
||||
import { getIntelligentSearchService } from "@/services/intelligent-search-service"
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const mindsDB = getMindsDBConnector()
|
||||
const searchService = getIntelligentSearchService()
|
||||
|
||||
// 获取系统状态
|
||||
const systemStatus = await mindsDB.getSystemStatus()
|
||||
const searchStats = searchService.getSearchStats()
|
||||
|
||||
return NextResponse.json({
|
||||
system: systemStatus,
|
||||
search: searchStats,
|
||||
timestamp: new Date().toISOString(),
|
||||
version: process.env.APP_VERSION || "1.0.0",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("系统状态API错误:", error)
|
||||
return NextResponse.json({ error: "获取系统状态失败" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { X } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Database,
|
||||
LayoutDashboard,
|
||||
Target,
|
||||
X,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
UserCheck,
|
||||
Layers,
|
||||
Tag,
|
||||
BrainCircuit,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Link from "next/link"
|
||||
|
||||
interface MobileSidebarProps {
|
||||
isOpen: boolean
|
||||
@@ -25,219 +10,43 @@ interface MobileSidebarProps {
|
||||
}
|
||||
|
||||
export default function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
|
||||
const pathname = usePathname()
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
"user-portrait": false, // 用户画像默认不展开
|
||||
})
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
setExpandedSections((prev) => ({
|
||||
...prev,
|
||||
[section]: !prev[section],
|
||||
}))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = "hidden"
|
||||
} else {
|
||||
document.body.style.overflow = "unset"
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = "unset"
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: "数据概览",
|
||||
href: "/",
|
||||
icon: <LayoutDashboard className="h-4 w-4" />,
|
||||
primary: true,
|
||||
description: "平台整体数据分析",
|
||||
},
|
||||
{
|
||||
title: "数据中台",
|
||||
href: "/data-platform",
|
||||
icon: <Database className="h-4 w-4" />,
|
||||
primary: true,
|
||||
tag: "核心",
|
||||
description: "多源数据整合中心",
|
||||
},
|
||||
{
|
||||
title: "用户画像", // 整合用户池功能
|
||||
href: "/user-portrait",
|
||||
icon: <Target className="h-4 w-4" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-portrait",
|
||||
description: "用户数据管理与画像分析",
|
||||
children: [
|
||||
{
|
||||
title: "用户管理", // 原用户池的用户管理
|
||||
href: "/user-portrait/management",
|
||||
icon: <UserCheck className="h-3 w-3" />,
|
||||
description: "IMEI、手机号管理",
|
||||
},
|
||||
{
|
||||
title: "用户分群", // 原用户池的用户分群
|
||||
href: "/user-portrait/segmentation",
|
||||
icon: <Layers className="h-3 w-3" />,
|
||||
description: "智能用户分群",
|
||||
},
|
||||
{
|
||||
title: "标签管理", // 原用户画像的标签管理
|
||||
href: "/user-portrait/tags",
|
||||
icon: <Tag className="h-3 w-3" />,
|
||||
description: "用户标签体系",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "AI智能助手", // 新增AI智能助手
|
||||
href: "/ai-assistant",
|
||||
icon: <BrainCircuit className="h-4 w-4" />,
|
||||
primary: true,
|
||||
description: "AI数据分析与营销策略",
|
||||
},
|
||||
]
|
||||
|
||||
const getTagColor = (tag: string) => {
|
||||
switch (tag) {
|
||||
case "核心":
|
||||
return "bg-blue-100 text-blue-700 border-blue-200"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-700 border-gray-200"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 背景遮罩 */}
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 bg-black/20 backdrop-blur-sm z-40 transition-opacity duration-300",
|
||||
isOpen ? "opacity-100" : "opacity-0 pointer-events-none",
|
||||
)}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-40 transition-transform duration-300 md:hidden",
|
||||
isOpen ? "translate-x-0" : "translate-x-full",
|
||||
)}
|
||||
aria-hidden={!isOpen}
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={onClose} />
|
||||
|
||||
{/* 侧边栏 */}
|
||||
<div
|
||||
className={cn(
|
||||
"fixed left-0 top-0 h-full w-80 glass-nav safe-area-top safe-area-left z-50 transition-transform duration-300 ease-out",
|
||||
isOpen ? "translate-x-0" : "-translate-x-full",
|
||||
)}
|
||||
>
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-white/20">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-6 h-6 rounded-lg glass-light flex items-center justify-center">
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
<h1 className="text-sm font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
卡若数据资产中台
|
||||
</h1>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="glass-light rounded-lg h-8 w-8">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
{/* Drawer */}
|
||||
<aside className="relative ml-auto h-full w-64 bg-white shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h2 className="text-lg font-semibold">导航</h2>
|
||||
<button onClick={onClose} aria-label="Close menu" className="rounded p-1 hover:bg-gray-100">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 导航菜单 */}
|
||||
<div className="flex-1 overflow-y-auto py-2">
|
||||
<nav className="space-y-1 px-3">
|
||||
{navItems.map((item) => (
|
||||
<div key={item.href}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-3 py-2 text-xs font-medium rounded-lg transition-all duration-300 group",
|
||||
pathname === item.href || (item.children && pathname.startsWith(item.href))
|
||||
? "glass-heavy text-blue-700 shadow-glass"
|
||||
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600",
|
||||
)}
|
||||
>
|
||||
<Link href={item.href} onClick={onClose} className="flex items-center flex-1 min-w-0">
|
||||
<div className="transition-colors duration-300 flex-shrink-0">{item.icon}</div>
|
||||
<div className="flex-1 ml-2 min-w-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-medium truncate">{item.title}</span>
|
||||
</div>
|
||||
{item.tag && (
|
||||
<Badge
|
||||
className={cn("text-xs px-1.5 py-0.5 rounded ml-1 flex-shrink-0", getTagColor(item.tag))}
|
||||
>
|
||||
{item.tag}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 truncate mt-0.5">{item.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
{item.expandable && (
|
||||
<button
|
||||
onClick={() => toggleSection(item.section!)}
|
||||
className="ml-1 p-1 hover:bg-white/20 rounded transition-colors duration-200 flex-shrink-0"
|
||||
>
|
||||
{expandedSections[item.section!] ? (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 子菜单 */}
|
||||
{item.children && expandedSections[item.section!] && (
|
||||
<div className="ml-4 mt-1 space-y-1">
|
||||
{item.children.map((subItem) => (
|
||||
<Link
|
||||
key={subItem.href}
|
||||
href={subItem.href}
|
||||
onClick={onClose}
|
||||
className={cn(
|
||||
"flex items-center px-2 py-1.5 text-xs rounded transition-all duration-300",
|
||||
pathname === subItem.href
|
||||
? "glass-light text-blue-600 shadow-glass-sm"
|
||||
: "text-gray-600 hover:glass-light hover:text-blue-500",
|
||||
)}
|
||||
>
|
||||
<div className="mr-2 text-gray-400 transition-colors duration-300 flex-shrink-0">
|
||||
{subItem.icon}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium truncate">{subItem.title}</p>
|
||||
<p className="text-xs text-gray-500 truncate">{subItem.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 底部信息 */}
|
||||
<div className="border-t border-white/20 p-3">
|
||||
<div className="glass-light rounded-lg p-2">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-gray-600">数据同步状态</span>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full mr-1"></div>
|
||||
<span className="text-green-600">实时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs mt-1">
|
||||
<span className="text-gray-600">用户池总数</span>
|
||||
<span className="text-gray-500">125,678</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
{/* Navigation links — keep in sync with Sidebar.tsx */}
|
||||
<nav className="flex flex-col gap-1 p-4">
|
||||
<Link href="/" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
数据概览
|
||||
</Link>
|
||||
<Link href="/data-platform" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
数据中台
|
||||
</Link>
|
||||
<Link href="/user-portrait" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
用户画像
|
||||
</Link>
|
||||
<Link href="/ai-assistant" className="rounded px-3 py-2 text-sm hover:bg-gray-100" onClick={onClose}>
|
||||
AI 智能助手
|
||||
</Link>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,124 +4,63 @@ import { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Database,
|
||||
LayoutDashboard,
|
||||
Settings,
|
||||
Target,
|
||||
ChevronLeft,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Tag,
|
||||
UserCheck,
|
||||
Layers,
|
||||
BrainCircuit,
|
||||
Smartphone,
|
||||
} from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { LayoutDashboard, Database, Users, BrainCircuit, Settings, ChevronLeft } from "lucide-react"
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname()
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
"user-portrait": true, // 用户画像默认展开
|
||||
})
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setExpanded(!expanded)
|
||||
}
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
setExpandedSections((prev) => ({
|
||||
...prev,
|
||||
[section]: !prev[section],
|
||||
}))
|
||||
}
|
||||
|
||||
// 重新设计的导航结构 - 遵循新规则
|
||||
// 简化的导航结构
|
||||
const navItems = [
|
||||
{
|
||||
title: "数据概览",
|
||||
href: "/",
|
||||
icon: <LayoutDashboard className="h-5 w-5" />,
|
||||
primary: true,
|
||||
description: "平台整体数据分析与监控",
|
||||
},
|
||||
{
|
||||
title: "数据中台",
|
||||
href: "/data-platform",
|
||||
icon: <Database className="h-5 w-5" />,
|
||||
primary: true,
|
||||
tag: "核心",
|
||||
description: "多源数据整合与处理中心",
|
||||
},
|
||||
{
|
||||
title: "用户画像", // 整合用户池功能
|
||||
title: "用户画像",
|
||||
href: "/user-portrait",
|
||||
icon: <Target className="h-5 w-5" />,
|
||||
primary: true,
|
||||
expandable: true,
|
||||
section: "user-portrait",
|
||||
children: [
|
||||
{
|
||||
title: "用户管理", // 原用户池的用户管理
|
||||
href: "/user-portrait/management",
|
||||
icon: <UserCheck className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "用户分群", // 原用户池的用户分群
|
||||
href: "/user-portrait/segmentation",
|
||||
icon: <Layers className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
title: "标签管理", // 原用户画像的标签管理
|
||||
href: "/user-portrait/tags",
|
||||
icon: <Tag className="h-4 w-4" />,
|
||||
},
|
||||
],
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
description: "用户数据管理与画像分析",
|
||||
},
|
||||
{
|
||||
title: "设备管理", // 提升为一级菜单 [^2]
|
||||
href: "/devices",
|
||||
icon: <Smartphone className="h-5 w-5" />,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
title: "AI智能助手", // 新增AI智能助手
|
||||
title: "AI智能助手",
|
||||
href: "/ai-assistant",
|
||||
icon: <BrainCircuit className="h-5 w-5" />,
|
||||
primary: true,
|
||||
description: "AI数据分析与营销策略",
|
||||
},
|
||||
]
|
||||
|
||||
const getTagColor = (tag: string) => {
|
||||
switch (tag) {
|
||||
case "核心":
|
||||
return "glass-light text-orange-700 border-orange-200"
|
||||
default:
|
||||
return "glass-light text-gray-700 border-gray-200"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col h-screen glass-nav m-4 transition-all duration-300 ease-in-out",
|
||||
"flex flex-col h-screen bg-white border-r border-gray-200 transition-all duration-300 ease-in-out",
|
||||
expanded ? "w-72" : "w-20",
|
||||
)}
|
||||
>
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center h-16 px-6 border-b border-white/20">
|
||||
<div className="flex items-center h-16 px-6 border-b border-gray-200">
|
||||
{expanded ? (
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 rounded-lg glass-light flex items-center justify-center">
|
||||
<div className="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center">
|
||||
<Database className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
<h1 className="text-lg font-semibold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
卡若数据资产中台
|
||||
</h1>
|
||||
<h1 className="text-lg font-semibold text-gray-900">数据资产中台</h1>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mx-auto">
|
||||
<div className="w-8 h-8 rounded-lg glass-light flex items-center justify-center">
|
||||
<div className="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center">
|
||||
<Database className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,86 +71,49 @@ export default function Sidebar() {
|
||||
<div className="flex-1 overflow-y-auto py-4">
|
||||
<nav className="space-y-2 px-4">
|
||||
{navItems.map((item) => (
|
||||
<div key={item.href}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-300 group",
|
||||
pathname === item.href || (item.children && pathname.startsWith(item.href))
|
||||
? "glass-heavy text-blue-700 shadow-glass"
|
||||
: "glass-light text-gray-700 hover:glass-heavy hover:text-blue-600 hover:scale-105",
|
||||
)}
|
||||
>
|
||||
<Link href={item.href} className={cn("flex items-center flex-1", !expanded && "justify-center")}>
|
||||
<div className="transition-colors duration-300">{item.icon}</div>
|
||||
{expanded && (
|
||||
<div className="flex-1 flex items-center justify-between ml-3">
|
||||
<span className="font-medium">{item.title}</span>
|
||||
{item.tag && (
|
||||
<Badge className={cn("text-xs px-2 py-1 rounded-lg", getTagColor(item.tag))}>{item.tag}</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
{expanded && item.expandable && (
|
||||
<button
|
||||
onClick={() => toggleSection(item.section!)}
|
||||
className="ml-2 p-1 hover:bg-white/20 rounded-lg transition-colors duration-200"
|
||||
>
|
||||
{expandedSections[item.section!] ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 子菜单 */}
|
||||
{expanded && item.children && expandedSections[item.section!] && (
|
||||
<div className="ml-6 mt-2 space-y-1">
|
||||
{item.children.map((subItem) => (
|
||||
<Link
|
||||
key={subItem.href}
|
||||
href={subItem.href}
|
||||
className={cn(
|
||||
"flex items-center px-3 py-2 text-sm rounded-lg transition-all duration-300",
|
||||
pathname === subItem.href
|
||||
? "glass-light text-blue-600 shadow-glass-sm"
|
||||
: "text-gray-600 hover:glass-light hover:text-blue-500",
|
||||
)}
|
||||
>
|
||||
<div className="mr-3 text-gray-400 transition-colors duration-300">{subItem.icon}</div>
|
||||
<span>{subItem.title}</span>
|
||||
</Link>
|
||||
))}
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 group",
|
||||
pathname === item.href
|
||||
? "bg-blue-50 text-blue-700 border border-blue-200"
|
||||
: "text-gray-700 hover:bg-gray-50 hover:text-blue-600",
|
||||
)}
|
||||
>
|
||||
<div className="transition-colors duration-200">{item.icon}</div>
|
||||
{expanded && (
|
||||
<div className="flex-1 ml-3">
|
||||
<div className="font-medium">{item.title}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{item.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 底部设置和折叠按钮 */}
|
||||
<div className="mt-auto border-t border-white/20">
|
||||
<div className="mt-auto border-t border-gray-200">
|
||||
<Link
|
||||
href="/settings"
|
||||
className={cn(
|
||||
"flex items-center px-4 py-2 mx-4 my-2 text-xs font-medium rounded-lg transition-all duration-300",
|
||||
"flex items-center px-4 py-3 mx-4 my-2 text-sm font-medium rounded-lg transition-all duration-200",
|
||||
pathname === "/settings"
|
||||
? "glass-heavy text-blue-700 shadow-glass"
|
||||
: "glass-light text-gray-600 hover:glass-heavy hover:text-blue-500",
|
||||
? "bg-blue-50 text-blue-700 border border-blue-200"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-blue-600",
|
||||
)}
|
||||
>
|
||||
<div className={cn("transition-colors duration-300", !expanded && "mx-auto")}>
|
||||
<div className={cn("transition-colors duration-200", !expanded && "mx-auto")}>
|
||||
<Settings className="h-4 w-4" />
|
||||
</div>
|
||||
{expanded && <span className="ml-2">设置</span>}
|
||||
{expanded && <span className="ml-2">系统设置</span>}
|
||||
</Link>
|
||||
|
||||
<div className="p-4">
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="w-full flex items-center justify-center p-3 rounded-xl glass-button hover:scale-105 transition-all duration-300"
|
||||
className="w-full flex items-center justify-center p-3 rounded-lg bg-gray-50 hover:bg-gray-100 transition-all duration-200"
|
||||
>
|
||||
<ChevronLeft
|
||||
className={cn(
|
||||
|
||||
514
app/data-ingestion/page.tsx
Normal file
514
app/data-ingestion/page.tsx
Normal file
@@ -0,0 +1,514 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
Database,
|
||||
Upload,
|
||||
Download,
|
||||
RefreshCw,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Clock,
|
||||
Settings,
|
||||
Pause,
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Zap,
|
||||
} from "lucide-react"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
|
||||
export default function DataIngestionPage() {
|
||||
const { toast } = useToast()
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
const [ingestionStatus, setIngestionStatus] = useState<any>(null)
|
||||
const [isManualIngesting, setIsManualIngesting] = useState(false)
|
||||
|
||||
// 模拟数据接入状态
|
||||
const [mockStatus] = useState({
|
||||
totalIngested: 125678,
|
||||
todayIngested: 1234,
|
||||
activeSources: 8,
|
||||
lastIngestionTime: "2分钟前",
|
||||
dataQuality: 94.6,
|
||||
processingQueue: 23,
|
||||
recentIngestions: [
|
||||
{
|
||||
id: "ing_001",
|
||||
source: "抖音API",
|
||||
recordsCount: 156,
|
||||
status: "completed",
|
||||
startTime: "14:30",
|
||||
duration: "2分钟",
|
||||
quality: 96.8,
|
||||
},
|
||||
{
|
||||
id: "ing_002",
|
||||
source: "触客宝后台",
|
||||
recordsCount: 89,
|
||||
status: "completed",
|
||||
startTime: "14:25",
|
||||
duration: "1分钟",
|
||||
quality: 95.2,
|
||||
},
|
||||
{
|
||||
id: "ing_003",
|
||||
source: "表单提交",
|
||||
recordsCount: 234,
|
||||
status: "processing",
|
||||
startTime: "14:32",
|
||||
duration: "进行中",
|
||||
quality: 0,
|
||||
},
|
||||
{
|
||||
id: "ing_004",
|
||||
source: "小红书API",
|
||||
recordsCount: 67,
|
||||
status: "failed",
|
||||
startTime: "14:20",
|
||||
duration: "失败",
|
||||
quality: 0,
|
||||
},
|
||||
],
|
||||
sourceStats: [
|
||||
{ name: "抖音API", records: 45678, quality: 96.8, status: "active" },
|
||||
{ name: "小红书API", records: 23456, quality: 92.5, status: "active" },
|
||||
{ name: "触客宝后台", records: 12345, quality: 95.1, status: "active" },
|
||||
{ name: "表单提交", records: 34567, quality: 97.2, status: "active" },
|
||||
{ name: "飞书妙记", records: 5678, quality: 91.3, status: "active" },
|
||||
{ name: "微信公众号", records: 8901, quality: 93.7, status: "inactive" },
|
||||
],
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// 模拟获取接入状态
|
||||
setIngestionStatus(mockStatus)
|
||||
}, [])
|
||||
|
||||
const handleManualIngest = async (formData: any) => {
|
||||
setIsManualIngesting(true)
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/ingest", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: "数据接入成功",
|
||||
description: `成功处理用户数据,用户ID: ${result.data.userId}`,
|
||||
})
|
||||
} else {
|
||||
throw new Error(result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "数据接入失败",
|
||||
description: (error as Error).message,
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsManualIngesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "processing":
|
||||
return "bg-yellow-100 text-yellow-800"
|
||||
case "failed":
|
||||
return "bg-red-100 text-red-800"
|
||||
case "active":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "inactive":
|
||||
return "bg-gray-100 text-gray-800"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return <CheckCircle className="h-4 w-4 text-green-600" />
|
||||
case "processing":
|
||||
return <Clock className="h-4 w-4 text-yellow-600 animate-spin" />
|
||||
case "failed":
|
||||
return <AlertCircle className="h-4 w-4 text-red-600" />
|
||||
case "active":
|
||||
return <CheckCircle className="h-4 w-4 text-green-600" />
|
||||
case "inactive":
|
||||
return <Pause className="h-4 w-4 text-gray-600" />
|
||||
default:
|
||||
return <AlertCircle className="h-4 w-4 text-gray-600" />
|
||||
}
|
||||
}
|
||||
|
||||
if (!ingestionStatus) {
|
||||
return <div className="container mx-auto py-8">加载中...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
|
||||
{/* 页面标题和工具栏 */}
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold">数据接入管理</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground mt-1">多源数据接入监控与管理</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
手动接入
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>手动数据接入</DialogTitle>
|
||||
<DialogDescription>手动提交数据进行接入处理</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ManualIngestionForm onSubmit={handleManualIngest} isLoading={isManualIngesting} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
导出日志
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="overview">概览</TabsTrigger>
|
||||
<TabsTrigger value="sources">数据源</TabsTrigger>
|
||||
<TabsTrigger value="history">接入历史</TabsTrigger>
|
||||
<TabsTrigger value="monitoring">监控统计</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-6">
|
||||
{/* 接入概览 */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">总接入量</p>
|
||||
<p className="text-2xl font-bold">{ingestionStatus.totalIngested.toLocaleString()}</p>
|
||||
</div>
|
||||
<Database className="h-8 w-8 text-blue-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">今日接入</p>
|
||||
<p className="text-2xl font-bold">{ingestionStatus.todayIngested.toLocaleString()}</p>
|
||||
</div>
|
||||
<TrendingUp className="h-8 w-8 text-green-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">活跃数据源</p>
|
||||
<p className="text-2xl font-bold">{ingestionStatus.activeSources}</p>
|
||||
</div>
|
||||
<Zap className="h-8 w-8 text-purple-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">数据质量</p>
|
||||
<p className="text-2xl font-bold">{ingestionStatus.dataQuality}%</p>
|
||||
</div>
|
||||
<BarChart3 className="h-8 w-8 text-orange-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 实时状态 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>实时接入状态</CardTitle>
|
||||
<CardDescription>当前数据接入处理状态</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm">处理队列</span>
|
||||
<Badge variant="outline">{ingestionStatus.processingQueue} 条待处理</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm">最后接入时间</span>
|
||||
<span className="text-sm font-medium">{ingestionStatus.lastIngestionTime}</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>数据质量评分</span>
|
||||
<span>{ingestionStatus.dataQuality}%</span>
|
||||
</div>
|
||||
<Progress value={ingestionStatus.dataQuality} className="h-2" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="sources" className="space-y-6">
|
||||
{/* 数据源状态 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>数据源管理</CardTitle>
|
||||
<CardDescription>各数据源的接入状态和质量监控</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{ingestionStatus.sourceStats.map((source: any, index: number) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div className="flex items-center gap-4">
|
||||
{getStatusIcon(source.status)}
|
||||
<div>
|
||||
<h3 className="font-medium">{source.name}</h3>
|
||||
<p className="text-sm text-muted-foreground">{source.records.toLocaleString()} 条记录</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium">{source.quality}%</p>
|
||||
<p className="text-xs text-muted-foreground">数据质量</p>
|
||||
</div>
|
||||
<Badge className={getStatusColor(source.status)}>
|
||||
{source.status === "active" ? "活跃" : "非活跃"}
|
||||
</Badge>
|
||||
<Button variant="outline" size="sm">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="history" className="space-y-6">
|
||||
{/* 接入历史 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>最近接入记录</CardTitle>
|
||||
<CardDescription>最近的数据接入处理记录</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{ingestionStatus.recentIngestions.map((ingestion: any) => (
|
||||
<div key={ingestion.id} className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div className="flex items-center gap-4">
|
||||
{getStatusIcon(ingestion.status)}
|
||||
<div>
|
||||
<h3 className="font-medium">{ingestion.source}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{ingestion.recordsCount} 条记录 • {ingestion.startTime} • {ingestion.duration}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{ingestion.quality > 0 && (
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium">{ingestion.quality}%</p>
|
||||
<p className="text-xs text-muted-foreground">质量评分</p>
|
||||
</div>
|
||||
)}
|
||||
<Badge className={getStatusColor(ingestion.status)}>
|
||||
{ingestion.status === "completed"
|
||||
? "完成"
|
||||
: ingestion.status === "processing"
|
||||
? "处理中"
|
||||
: "失败"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="monitoring" className="space-y-6">
|
||||
{/* 监控统计 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>接入趋势</CardTitle>
|
||||
<CardDescription>过去7天的数据接入趋势</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px] flex items-center justify-center text-muted-foreground">
|
||||
<BarChart3 className="h-12 w-12 mb-2" />
|
||||
<p>接入趋势图表</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>质量分布</CardTitle>
|
||||
<CardDescription>各数据源的质量分布情况</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{ingestionStatus.sourceStats.slice(0, 5).map((source: any, index: number) => (
|
||||
<div key={index} className="space-y-1">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>{source.name}</span>
|
||||
<span>{source.quality}%</span>
|
||||
</div>
|
||||
<Progress value={source.quality} className="h-2" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 手动接入表单组件
|
||||
function ManualIngestionForm({ onSubmit, isLoading }: { onSubmit: (data: any) => void; isLoading: boolean }) {
|
||||
const [formData, setFormData] = useState({
|
||||
source: "",
|
||||
sourceUserId: "",
|
||||
sourceRecordId: "",
|
||||
originalData: "",
|
||||
})
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(formData.originalData)
|
||||
onSubmit({
|
||||
...formData,
|
||||
originalData: parsedData,
|
||||
})
|
||||
} catch (error) {
|
||||
alert("原始数据必须是有效的JSON格式")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="source">数据源</Label>
|
||||
<Select value={formData.source} onValueChange={(value) => setFormData({ ...formData, source: value })}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择数据源" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="douyin">抖音</SelectItem>
|
||||
<SelectItem value="xiaohongshu">小红书</SelectItem>
|
||||
<SelectItem value="cunkebao_form">存客宝表单</SelectItem>
|
||||
<SelectItem value="touchkebao_call">触客宝呼入</SelectItem>
|
||||
<SelectItem value="feishu_notes">飞书妙记</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sourceUserId">源用户ID</Label>
|
||||
<Input
|
||||
id="sourceUserId"
|
||||
value={formData.sourceUserId}
|
||||
onChange={(e) => setFormData({ ...formData, sourceUserId: e.target.value })}
|
||||
placeholder="可选"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sourceRecordId">源记录ID</Label>
|
||||
<Input
|
||||
id="sourceRecordId"
|
||||
value={formData.sourceRecordId}
|
||||
onChange={(e) => setFormData({ ...formData, sourceRecordId: e.target.value })}
|
||||
placeholder="可选"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="originalData">原始数据 (JSON格式)</Label>
|
||||
<Textarea
|
||||
id="originalData"
|
||||
value={formData.originalData}
|
||||
onChange={(e) => setFormData({ ...formData, originalData: e.target.value })}
|
||||
placeholder='{"name": "张三", "phone": "13800138000", "email": "zhangsan@example.com"}'
|
||||
rows={8}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" disabled={isLoading}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading || !formData.source || !formData.originalData}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Clock className="mr-2 h-4 w-4 animate-spin" />
|
||||
处理中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
提交接入
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -3,552 +3,403 @@
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Database, Server, Link, Plus, FileText, RefreshCw } from "lucide-react"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import {
|
||||
Database,
|
||||
GitBranch,
|
||||
Zap,
|
||||
Shield,
|
||||
Activity,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Clock,
|
||||
TrendingUp,
|
||||
Settings,
|
||||
Plus,
|
||||
} from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function DataIntegrationPage() {
|
||||
const [activeTab, setActiveTab] = useState("data-sources")
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState("overview")
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">数据中台</h1>
|
||||
<p className="text-muted-foreground">管理数据源和API接口</p>
|
||||
</div>
|
||||
</div>
|
||||
const integrationStats = {
|
||||
totalSources: 12,
|
||||
activeSources: 10,
|
||||
dailyVolume: 2456789,
|
||||
successRate: 98.5,
|
||||
avgLatency: 156,
|
||||
qualityScore: 94.2,
|
||||
}
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full max-w-md grid-cols-2">
|
||||
<TabsTrigger value="data-sources">数据集成</TabsTrigger>
|
||||
<TabsTrigger value="api-management">API管理</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="data-sources" className="space-y-6">
|
||||
<DataSourcesTab setIsDialogOpen={setIsDialogOpen} />
|
||||
<div className="mt-4">
|
||||
<Button variant="outline" asChild>
|
||||
<a href="/database-structure">
|
||||
<Database className="mr-2 h-4 w-4" />
|
||||
查看数据库结构
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="api-management" className="space-y-6">
|
||||
<ApiManagementTab />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<AddDataSourceDialog isOpen={isDialogOpen} setIsOpen={setIsDialogOpen} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 数据源管理标签页
|
||||
function DataSourcesTab({ setIsDialogOpen }: { setIsDialogOpen: (open: boolean) => void }) {
|
||||
// 模拟数据源列表
|
||||
const dataSources = [
|
||||
{
|
||||
id: "1",
|
||||
name: "用户数据库",
|
||||
type: "MySQL",
|
||||
host: "db.example.com",
|
||||
id: 1,
|
||||
name: "MySQL主库",
|
||||
type: "database",
|
||||
status: "connected",
|
||||
lastSync: "2023-07-20 15:30",
|
||||
tables: 24,
|
||||
records: 156789,
|
||||
lastSync: "2分钟前",
|
||||
records: 1245678,
|
||||
quality: 98.5,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "订单系统",
|
||||
type: "PostgreSQL",
|
||||
host: "orders.example.com",
|
||||
id: 2,
|
||||
name: "触客宝API",
|
||||
type: "api",
|
||||
status: "connected",
|
||||
lastSync: "2023-07-19 12:45",
|
||||
tables: 18,
|
||||
records: 89456,
|
||||
lastSync: "5分钟前",
|
||||
records: 456789,
|
||||
quality: 96.2,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "内容库",
|
||||
type: "MongoDB",
|
||||
host: "content.example.com",
|
||||
id: 3,
|
||||
name: "抖音内容平台",
|
||||
type: "api",
|
||||
status: "warning",
|
||||
lastSync: "1小时前",
|
||||
records: 234567,
|
||||
quality: 89.3,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "小红书API",
|
||||
type: "api",
|
||||
status: "connected",
|
||||
lastSync: "10分钟前",
|
||||
records: 123456,
|
||||
quality: 95.8,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "微信视频号",
|
||||
type: "api",
|
||||
status: "connected",
|
||||
lastSync: "15分钟前",
|
||||
records: 98765,
|
||||
quality: 92.4,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "飞书妙记API",
|
||||
type: "api",
|
||||
status: "error",
|
||||
lastSync: "2023-07-15 09:20",
|
||||
tables: 12,
|
||||
lastSync: "2小时前",
|
||||
records: 45678,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "用户行为分析",
|
||||
type: "ClickHouse",
|
||||
host: "analytics.example.com",
|
||||
status: "connected",
|
||||
lastSync: "2023-07-20 10:15",
|
||||
tables: 8,
|
||||
records: 2345678,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "CRM系统",
|
||||
type: "Oracle",
|
||||
host: "crm.example.com",
|
||||
status: "pending",
|
||||
lastSync: "等待连接",
|
||||
tables: 0,
|
||||
records: 0,
|
||||
quality: 78.9,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-2xl font-bold">数据源管理</h2>
|
||||
<Button onClick={() => setIsDialogOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
添加数据源
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>已连接的数据源</CardTitle>
|
||||
<CardDescription>管理和监控所有数据源连接</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>数据源名称</TableHead>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>主机地址</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>最后同步</TableHead>
|
||||
<TableHead>表数量</TableHead>
|
||||
<TableHead>记录数</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{dataSources.map((source) => (
|
||||
<TableRow key={source.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center">
|
||||
<Database className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||
<span className="font-medium">{source.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{source.type}</TableCell>
|
||||
<TableCell>{source.host}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
className={
|
||||
source.status === "connected"
|
||||
? "bg-green-100 text-green-800"
|
||||
: source.status === "error"
|
||||
? "bg-red-100 text-red-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}
|
||||
>
|
||||
{source.status === "connected" ? "已连接" : source.status === "error" ? "错误" : "等待中"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{source.lastSync}</TableCell>
|
||||
<TableCell>{source.tables}</TableCell>
|
||||
<TableCell>{source.records.toLocaleString()}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Link className="h-4 w-4 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
<Button size="sm">
|
||||
<RefreshCw className="h-4 w-4 mr-1" />
|
||||
同步
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>数据源类型</CardTitle>
|
||||
<CardDescription>支持的数据库和数据源类型</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Database className="h-8 w-8 mb-2 text-blue-500" />
|
||||
<span className="font-medium">MySQL</span>
|
||||
<span className="text-xs text-muted-foreground">关系型数据库</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Database className="h-8 w-8 mb-2 text-blue-500" />
|
||||
<span className="font-medium">PostgreSQL</span>
|
||||
<span className="text-xs text-muted-foreground">关系型数据库</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Database className="h-8 w-8 mb-2 text-green-500" />
|
||||
<span className="font-medium">MongoDB</span>
|
||||
<span className="text-xs text-muted-foreground">文档型数据库</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Database className="h-8 w-8 mb-2 text-yellow-500" />
|
||||
<span className="font-medium">ClickHouse</span>
|
||||
<span className="text-xs text-muted-foreground">列式数据库</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Database className="h-8 w-8 mb-2 text-red-500" />
|
||||
<span className="font-medium">Oracle</span>
|
||||
<span className="text-xs text-muted-foreground">关系型数据库</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Database className="h-8 w-8 mb-2 text-purple-500" />
|
||||
<span className="font-medium">SQL Server</span>
|
||||
<span className="text-xs text-muted-foreground">关系型数据库</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<Server className="h-8 w-8 mb-2 text-gray-500" />
|
||||
<span className="font-medium">Redis</span>
|
||||
<span className="text-xs text-muted-foreground">键值存储</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-4 border rounded-lg">
|
||||
<FileText className="h-8 w-8 mb-2 text-gray-500" />
|
||||
<span className="font-medium">CSV/Excel</span>
|
||||
<span className="text-xs text-muted-foreground">文件导入</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// API管理标签页
|
||||
function ApiManagementTab() {
|
||||
// 模拟API接口数据
|
||||
const apiEndpoints = [
|
||||
const recentActivities = [
|
||||
{
|
||||
id: "1",
|
||||
name: "用户数据API",
|
||||
endpoint: "/api/users",
|
||||
method: "GET",
|
||||
category: "用户画像",
|
||||
status: "active",
|
||||
calls: 12567,
|
||||
lastCalled: "2023-07-20 16:45",
|
||||
id: 1,
|
||||
type: "sync_complete",
|
||||
message: "MySQL主库数据同步完成",
|
||||
time: "2分钟前",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "用户标签API",
|
||||
endpoint: "/api/users/tags",
|
||||
method: "GET",
|
||||
category: "用户画像",
|
||||
status: "active",
|
||||
calls: 8945,
|
||||
lastCalled: "2023-07-20 15:30",
|
||||
id: 2,
|
||||
type: "quality_check",
|
||||
message: "触客宝API数据质量检查通过",
|
||||
time: "8分钟前",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "流量池数据API",
|
||||
endpoint: "/api/traffic-pools",
|
||||
method: "GET",
|
||||
category: "流量池",
|
||||
status: "active",
|
||||
calls: 5678,
|
||||
lastCalled: "2023-07-20 14:20",
|
||||
id: 3,
|
||||
type: "sync_warning",
|
||||
message: "抖音内容平台同步延迟",
|
||||
time: "1小时前",
|
||||
status: "warning",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "AI分析API",
|
||||
endpoint: "/api/ai/analyze",
|
||||
method: "POST",
|
||||
category: "AI分析",
|
||||
status: "active",
|
||||
calls: 3456,
|
||||
lastCalled: "2023-07-20 13:15",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "数据同步API",
|
||||
endpoint: "/api/sync",
|
||||
method: "POST",
|
||||
category: "数据集成",
|
||||
status: "maintenance",
|
||||
calls: 2345,
|
||||
lastCalled: "2023-07-19 10:30",
|
||||
id: 4,
|
||||
type: "sync_error",
|
||||
message: "飞书妙记API连接失败",
|
||||
time: "2小时前",
|
||||
status: "error",
|
||||
},
|
||||
]
|
||||
|
||||
// 模拟API密钥数据
|
||||
const apiKeys = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Web应用",
|
||||
key: "sk_web_*************",
|
||||
created: "2023-05-15",
|
||||
lastUsed: "2023-07-20",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "移动应用",
|
||||
key: "sk_mobile_*************",
|
||||
created: "2023-06-10",
|
||||
lastUsed: "2023-07-19",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "第三方集成",
|
||||
key: "sk_partner_*************",
|
||||
created: "2023-04-20",
|
||||
lastUsed: "2023-07-18",
|
||||
status: "active",
|
||||
},
|
||||
]
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "connected":
|
||||
return <CheckCircle className="h-4 w-4 text-green-500" />
|
||||
case "warning":
|
||||
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
|
||||
case "error":
|
||||
return <AlertTriangle className="h-4 w-4 text-red-500" />
|
||||
default:
|
||||
return <Clock className="h-4 w-4 text-gray-500" />
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "connected":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "warning":
|
||||
return "bg-yellow-100 text-yellow-800"
|
||||
case "error":
|
||||
return "bg-red-100 text-red-800"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
}
|
||||
|
||||
const getActivityIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "sync_complete":
|
||||
return <CheckCircle className="h-4 w-4 text-green-500" />
|
||||
case "quality_check":
|
||||
return <Shield className="h-4 w-4 text-blue-500" />
|
||||
case "sync_warning":
|
||||
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
|
||||
case "sync_error":
|
||||
return <AlertTriangle className="h-4 w-4 text-red-500" />
|
||||
default:
|
||||
return <Activity className="h-4 w-4 text-gray-500" />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-2xl font-bold">API接口管理</h2>
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
创建新API
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>API接口列表</CardTitle>
|
||||
<CardDescription>所有可用的API接口</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>API名称</TableHead>
|
||||
<TableHead>接口地址</TableHead>
|
||||
<TableHead>方法</TableHead>
|
||||
<TableHead>分类</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>调用次数</TableHead>
|
||||
<TableHead>最后调用</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apiEndpoints.map((api) => (
|
||||
<TableRow key={api.id}>
|
||||
<TableCell className="font-medium">{api.name}</TableCell>
|
||||
<TableCell>
|
||||
<code className="bg-muted px-1 py-0.5 rounded text-sm">{api.endpoint}</code>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
className={
|
||||
api.method === "GET"
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: api.method === "POST"
|
||||
? "bg-green-100 text-green-800"
|
||||
: api.method === "PUT"
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
}
|
||||
>
|
||||
{api.method}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{api.category}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
className={
|
||||
api.status === "active" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"
|
||||
}
|
||||
>
|
||||
{api.status === "active" ? "正常" : "维护中"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{api.calls.toLocaleString()}</TableCell>
|
||||
<TableCell>{api.lastCalled}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm">
|
||||
文档
|
||||
</Button>
|
||||
<Button size="sm">测试</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>API密钥管理</CardTitle>
|
||||
<CardDescription>管理API访问密钥</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>应用名称</TableHead>
|
||||
<TableHead>密钥</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>最后使用</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apiKeys.map((key) => (
|
||||
<TableRow key={key.id}>
|
||||
<TableCell className="font-medium">{key.name}</TableCell>
|
||||
<TableCell>
|
||||
<code className="bg-muted px-1 py-0.5 rounded text-sm">{key.key}</code>
|
||||
</TableCell>
|
||||
<TableCell>{key.created}</TableCell>
|
||||
<TableCell>{key.lastUsed}</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">{key.status === "active" ? "有效" : "已禁用"}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm">
|
||||
重置
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="text-red-500">
|
||||
撤销
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>API使用统计</CardTitle>
|
||||
<CardDescription>API调用量和性能统计</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="h-80 flex items-center justify-center">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<p>API调用统计图表</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 添加数据源对话框
|
||||
function AddDataSourceDialog({ isOpen, setIsOpen }: { isOpen: boolean; setIsOpen: (open: boolean) => void }) {
|
||||
const [dbType, setDbType] = useState("mysql")
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加新数据源</DialogTitle>
|
||||
<DialogDescription>连接到新的数据库或数据源</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="db-type" className="text-right">
|
||||
数据库类型
|
||||
</Label>
|
||||
<Select value={dbType} onValueChange={setDbType} className="col-span-3">
|
||||
<SelectTrigger id="db-type">
|
||||
<SelectValue placeholder="选择数据库类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="mysql">MySQL</SelectItem>
|
||||
<SelectItem value="postgresql">PostgreSQL</SelectItem>
|
||||
<SelectItem value="mongodb">MongoDB</SelectItem>
|
||||
<SelectItem value="clickhouse">ClickHouse</SelectItem>
|
||||
<SelectItem value="oracle">Oracle</SelectItem>
|
||||
<SelectItem value="sqlserver">SQL Server</SelectItem>
|
||||
<SelectItem value="redis">Redis</SelectItem>
|
||||
<SelectItem value="file">CSV/Excel文件</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
数据源名称
|
||||
</Label>
|
||||
<Input id="name" placeholder="给数据源起个名字" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="host" className="text-right">
|
||||
主机地址
|
||||
</Label>
|
||||
<Input id="host" placeholder="例如: db.example.com" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="port" className="text-right">
|
||||
端口
|
||||
</Label>
|
||||
<Input
|
||||
id="port"
|
||||
placeholder={dbType === "mysql" ? "3306" : dbType === "postgresql" ? "5432" : "27017"}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="database" className="text-right">
|
||||
数据库名
|
||||
</Label>
|
||||
<Input id="database" placeholder="数据库名称" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">
|
||||
用户名
|
||||
</Label>
|
||||
<Input id="username" placeholder="数据库用户名" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="password" className="text-right">
|
||||
密码
|
||||
</Label>
|
||||
<Input id="password" type="password" placeholder="数据库密码" className="col-span-3" />
|
||||
</div>
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">数据对接</h1>
|
||||
<p className="text-muted-foreground">数据源管理与全流程监控</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
||||
取消
|
||||
<div className="flex gap-2">
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
添加数据源
|
||||
</Button>
|
||||
<Button type="submit">测试连接</Button>
|
||||
<Button type="submit">保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="outline">
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
配置管理
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 核心指标 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<Card className="bg-gradient-to-r from-blue-50 to-cyan-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">数据源总数</CardTitle>
|
||||
<Database className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-blue-600">{integrationStats.totalSources}</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">活跃: {integrationStats.activeSources}个</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">日处理量</CardTitle>
|
||||
<Activity className="h-4 w-4 text-green-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-green-600">
|
||||
{(integrationStats.dailyVolume / 1000000).toFixed(1)}M
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">条记录</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-pink-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">成功率</CardTitle>
|
||||
<TrendingUp className="h-4 w-4 text-purple-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-purple-600">{integrationStats.successRate}%</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">同步成功率</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-orange-50 to-red-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">平均延迟</CardTitle>
|
||||
<Zap className="h-4 w-4 text-orange-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-orange-600">{integrationStats.avgLatency}ms</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">响应时间</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 数据源状态 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<Card className="lg:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<GitBranch className="h-5 w-5 text-blue-600" />
|
||||
数据源状态
|
||||
</CardTitle>
|
||||
<CardDescription>各数据源连接状态与同步情况</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{dataSources.map((source) => (
|
||||
<div key={source.id} className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
{getStatusIcon(source.status)}
|
||||
<div>
|
||||
<p className="font-medium text-sm">{source.name}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{source.type === "database" ? "数据库" : "API接口"} • 最后同步: {source.lastSync}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Badge className={getStatusColor(source.status)}>
|
||||
{source.status === "connected" ? "已连接" : source.status === "warning" ? "警告" : "错误"}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-4 mt-1 text-xs text-muted-foreground">
|
||||
<span>{source.records.toLocaleString()} 条</span>
|
||||
<span>质量: {source.quality}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between mt-4">
|
||||
<Link href="/data-integration/sources">
|
||||
<Button variant="outline">管理数据源</Button>
|
||||
</Link>
|
||||
<Button>添加新数据源</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 实时活动 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 text-green-600" />
|
||||
实时活动
|
||||
</CardTitle>
|
||||
<CardDescription>数据同步活动日志</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{recentActivities.map((activity) => (
|
||||
<div key={activity.id} className="flex items-start gap-3 p-2 hover:bg-gray-50 rounded-lg">
|
||||
{getActivityIcon(activity.type)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{activity.message}</p>
|
||||
<p className="text-xs text-gray-500">{activity.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button variant="outline" className="w-full mt-4 bg-transparent">
|
||||
查看全部日志
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 数据流程概览 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-purple-600" />
|
||||
数据流程概览
|
||||
</CardTitle>
|
||||
<CardDescription>数据从接入到处理的完整流程</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<Link href="/data-integration/sources">
|
||||
<div className="text-center p-4 border rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">
|
||||
<GitBranch className="h-8 w-8 mx-auto mb-2 text-blue-600" />
|
||||
<h3 className="font-medium mb-1">数据源管理</h3>
|
||||
<p className="text-xs text-muted-foreground">配置和管理各种数据源</p>
|
||||
<Badge className="mt-2 bg-blue-100 text-blue-700">{integrationStats.totalSources}个源</Badge>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/data-integration/ingestion">
|
||||
<div className="text-center p-4 border rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">
|
||||
<Database className="h-8 w-8 mx-auto mb-2 text-green-600" />
|
||||
<h3 className="font-medium mb-1">数据接入</h3>
|
||||
<p className="text-xs text-muted-foreground">实时数据采集和同步</p>
|
||||
<Badge className="mt-2 bg-green-100 text-green-700">
|
||||
{(integrationStats.dailyVolume / 1000000).toFixed(1)}M/日
|
||||
</Badge>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/data-integration/processing">
|
||||
<div className="text-center p-4 border rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">
|
||||
<Zap className="h-8 w-8 mx-auto mb-2 text-purple-600" />
|
||||
<h3 className="font-medium mb-1">数据处理</h3>
|
||||
<p className="text-xs text-muted-foreground">清洗、转换和标准化</p>
|
||||
<Badge className="mt-2 bg-purple-100 text-purple-700">{integrationStats.successRate}%成功</Badge>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/data-integration/quality">
|
||||
<div className="text-center p-4 border rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">
|
||||
<Shield className="h-8 w-8 mx-auto mb-2 text-orange-600" />
|
||||
<h3 className="font-medium mb-1">质量监控</h3>
|
||||
<p className="text-xs text-muted-foreground">数据质量评估和监控</p>
|
||||
<Badge className="mt-2 bg-orange-100 text-orange-700">{integrationStats.qualityScore}%质量</Badge>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 性能监控 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5 text-green-600" />
|
||||
性能监控
|
||||
</CardTitle>
|
||||
<CardDescription>数据处理性能指标</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>数据处理速度</span>
|
||||
<span className="font-medium">85%</span>
|
||||
</div>
|
||||
<Progress value={85} className="h-2 mb-1" />
|
||||
<p className="text-xs text-muted-foreground">1.2M 记录/小时</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>系统资源使用</span>
|
||||
<span className="font-medium">67%</span>
|
||||
</div>
|
||||
<Progress value={67} className="h-2 mb-1" />
|
||||
<p className="text-xs text-muted-foreground">CPU + 内存</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>存储使用率</span>
|
||||
<span className="font-medium">42%</span>
|
||||
</div>
|
||||
<Progress value={42} className="h-2 mb-1" />
|
||||
<p className="text-xs text-muted-foreground">2.1TB / 5TB</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
567
app/intelligent-search/page.tsx
Normal file
567
app/intelligent-search/page.tsx
Normal file
@@ -0,0 +1,567 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useCallback } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Avatar } from "@/components/ui/avatar"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import {
|
||||
Search,
|
||||
Sparkles,
|
||||
Clock,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Brain,
|
||||
Zap,
|
||||
Download,
|
||||
RefreshCw,
|
||||
Eye,
|
||||
BarChart3,
|
||||
Lightbulb,
|
||||
} from "lucide-react"
|
||||
import { TooltipHelp, TooltipProvider } from "@/components/ui/tooltip-help"
|
||||
|
||||
interface SearchResult {
|
||||
id: string
|
||||
type: "user" | "traffic" | "insight"
|
||||
title: string
|
||||
description: string
|
||||
tags: string[]
|
||||
relevanceScore: number
|
||||
updatedAt: string
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
interface SearchResponse {
|
||||
results: SearchResult[]
|
||||
stats: {
|
||||
totalResults: number
|
||||
queryTime: number
|
||||
suggestions: string[]
|
||||
filters: Record<string, any>
|
||||
}
|
||||
hasMore: boolean
|
||||
}
|
||||
|
||||
export default function IntelligentSearchPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [searchType, setSearchType] = useState<"all" | "user" | "traffic">("all")
|
||||
const [useAI, setUseAI] = useState(true)
|
||||
const [includeInsights, setIncludeInsights] = useState(true)
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
const [searchResults, setSearchResults] = useState<SearchResponse | null>(null)
|
||||
const [searchHistory, setSearchHistory] = useState<string[]>([])
|
||||
const [activeTab, setActiveTab] = useState("search")
|
||||
|
||||
// 执行搜索
|
||||
const performSearch = useCallback(
|
||||
async (query: string) => {
|
||||
if (!query.trim()) return
|
||||
|
||||
setIsSearching(true)
|
||||
try {
|
||||
const response = await fetch("/api/search", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
type: searchType,
|
||||
options: {
|
||||
useAI,
|
||||
includeInsights,
|
||||
limit: 50,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("搜索失败")
|
||||
}
|
||||
|
||||
const data: SearchResponse = await response.json()
|
||||
setSearchResults(data)
|
||||
|
||||
// 添加到搜索历史
|
||||
setSearchHistory((prev) => {
|
||||
const newHistory = [query, ...prev.filter((h) => h !== query)].slice(0, 10)
|
||||
return newHistory
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("搜索错误:", error)
|
||||
} finally {
|
||||
setIsSearching(false)
|
||||
}
|
||||
},
|
||||
[searchType, useAI, includeInsights],
|
||||
)
|
||||
|
||||
// 处理搜索输入
|
||||
const handleSearch = () => {
|
||||
performSearch(searchQuery)
|
||||
}
|
||||
|
||||
// 处理回车键搜索
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSearch()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取类型图标
|
||||
const getTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "user":
|
||||
return <Users className="h-4 w-4 text-blue-500" />
|
||||
case "traffic":
|
||||
return <TrendingUp className="h-4 w-4 text-green-500" />
|
||||
case "insight":
|
||||
return <Lightbulb className="h-4 w-4 text-purple-500" />
|
||||
default:
|
||||
return <Search className="h-4 w-4 text-gray-500" />
|
||||
}
|
||||
}
|
||||
|
||||
// 获取类型标签颜色
|
||||
const getTypeBadgeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case "user":
|
||||
return "bg-blue-100 text-blue-800"
|
||||
case "traffic":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "insight":
|
||||
return "bg-purple-100 text-purple-800"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold flex items-center gap-2">
|
||||
<Brain className="h-8 w-8 text-purple-600" />
|
||||
智能搜索引擎
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">AI驱动的亚秒级数据搜索与洞察分析</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
导出结果
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<BarChart3 className="h-4 w-4 mr-2" />
|
||||
搜索分析
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索配置卡片 */}
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-blue-50 border-none shadow-lg">
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
{/* 主搜索框 */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="输入搜索关键词,支持自然语言查询..."
|
||||
className="pl-10 h-12 text-base border-2 border-purple-200 focus:border-purple-400"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSearch}
|
||||
disabled={isSearching || !searchQuery.trim()}
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2"
|
||||
size="sm"
|
||||
>
|
||||
{isSearching ? <RefreshCw className="h-4 w-4 animate-spin" /> : <Search className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 搜索选项 */}
|
||||
<div className="flex flex-wrap items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="search-type">搜索类型:</Label>
|
||||
<select
|
||||
id="search-type"
|
||||
value={searchType}
|
||||
onChange={(e) => setSearchType(e.target.value as any)}
|
||||
className="px-3 py-1 border rounded-md text-sm"
|
||||
>
|
||||
<option value="all">全部</option>
|
||||
<option value="user">用户数据</option>
|
||||
<option value="traffic">流量关键词</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch id="use-ai" checked={useAI} onCheckedChange={setUseAI} />
|
||||
<Label htmlFor="use-ai" className="flex items-center gap-1">
|
||||
<Sparkles className="h-4 w-4 text-purple-500" />
|
||||
AI增强搜索
|
||||
<TooltipHelp content="启用AI增强搜索,提供更智能的查询理解和结果优化" />
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch id="include-insights" checked={includeInsights} onCheckedChange={setIncludeInsights} />
|
||||
<Label htmlFor="include-insights" className="flex items-center gap-1">
|
||||
<Brain className="h-4 w-4 text-blue-500" />
|
||||
包含AI洞察
|
||||
<TooltipHelp content="在搜索结果中包含AI生成的业务洞察和分析建议" />
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索历史 */}
|
||||
{searchHistory.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">最近搜索:</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{searchHistory.slice(0, 5).map((query, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs bg-transparent"
|
||||
onClick={() => {
|
||||
setSearchQuery(query)
|
||||
performSearch(query)
|
||||
}}
|
||||
>
|
||||
{query}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 搜索结果 */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="search">搜索结果</TabsTrigger>
|
||||
<TabsTrigger value="insights">AI洞察</TabsTrigger>
|
||||
<TabsTrigger value="analytics">搜索分析</TabsTrigger>
|
||||
<TabsTrigger value="history">搜索历史</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 搜索结果标签页 */}
|
||||
<TabsContent value="search" className="space-y-4">
|
||||
{searchResults && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Search className="h-5 w-5" />
|
||||
搜索结果
|
||||
<Badge variant="outline">{searchResults.stats.totalResults} 个结果</Badge>
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Zap className="h-4 w-4" />
|
||||
查询耗时: {searchResults.stats.queryTime}ms
|
||||
</div>
|
||||
</div>
|
||||
{searchResults.stats.suggestions.length > 0 && (
|
||||
<CardDescription>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<span>相关建议:</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{searchResults.stats.suggestions.map((suggestion, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs text-blue-600 hover:text-blue-800"
|
||||
onClick={() => {
|
||||
setSearchQuery(suggestion)
|
||||
performSearch(suggestion)
|
||||
}}
|
||||
>
|
||||
{suggestion}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardDescription>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{searchResults.results.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{searchResults.results.map((result) => (
|
||||
<div
|
||||
key={result.id}
|
||||
className="flex items-start gap-4 p-4 border rounded-lg hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
>
|
||||
{/* 类型图标 */}
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
{result.type === "user" ? (
|
||||
<Avatar className="h-10 w-10">
|
||||
<div className="w-full h-full bg-blue-100 flex items-center justify-center">
|
||||
<Users className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
</Avatar>
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
|
||||
{getTypeIcon(result.type)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h3 className="font-semibold text-lg truncate">{result.title}</h3>
|
||||
<Badge className={getTypeBadgeColor(result.type)}>
|
||||
{result.type === "user" ? "用户" : result.type === "traffic" ? "流量" : "洞察"}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
相关性: {(result.relevanceScore * 100).toFixed(1)}%
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-2 line-clamp-2">{result.description}</p>
|
||||
|
||||
{/* 标签 */}
|
||||
<div className="flex flex-wrap gap-1 mb-2">
|
||||
{result.tags.slice(0, 5).map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 元数据 */}
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{new Date(result.updatedAt).toLocaleString("zh-CN")}
|
||||
</span>
|
||||
{result.metadata?.searchType && <span>类型: {result.metadata.searchType}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex-shrink-0">
|
||||
<Button variant="outline" size="sm">
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
查看详情
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 加载更多 */}
|
||||
{searchResults.hasMore && (
|
||||
<div className="text-center pt-4">
|
||||
<Button variant="outline">加载更多结果</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-medium mb-2">未找到相关结果</h3>
|
||||
<p className="text-muted-foreground">尝试使用其他关键词或调整搜索条件</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 空状态 */}
|
||||
{!searchResults && (
|
||||
<Card>
|
||||
<CardContent className="text-center py-12">
|
||||
<Brain className="h-16 w-16 mx-auto mb-4 text-purple-400" />
|
||||
<h3 className="text-xl font-medium mb-2">开始智能搜索</h3>
|
||||
<p className="text-muted-foreground mb-4">输入关键词开始搜索,支持自然语言查询和AI增强分析</p>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{["高价值用户", "流量趋势", "用户行为分析", "RFM分群"].map((example) => (
|
||||
<Button
|
||||
key={example}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSearchQuery(example)
|
||||
performSearch(example)
|
||||
}}
|
||||
>
|
||||
{example}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* AI洞察标签页 */}
|
||||
<TabsContent value="insights" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-purple-600" />
|
||||
AI智能洞察
|
||||
<TooltipHelp content="基于搜索结果生成的AI洞察和业务建议" />
|
||||
</CardTitle>
|
||||
<CardDescription>AI分析搜索数据,提供深度业务洞察</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{searchResults?.results.filter((r) => r.type === "insight").length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{searchResults.results
|
||||
.filter((r) => r.type === "insight")
|
||||
.map((insight) => (
|
||||
<div key={insight.id} className="p-4 border rounded-lg bg-purple-50">
|
||||
<div className="flex items-start gap-3">
|
||||
<Lightbulb className="h-5 w-5 text-purple-600 mt-1" />
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium mb-2">{insight.title}</h4>
|
||||
<p className="text-sm text-muted-foreground mb-2">{insight.description}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
置信度: {(insight.relevanceScore * 100).toFixed(1)}%
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(insight.updatedAt).toLocaleString("zh-CN")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Sparkles className="h-12 w-12 mx-auto mb-4 text-purple-400 opacity-50" />
|
||||
<p className="text-muted-foreground">执行搜索后,AI将为您生成智能洞察</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* 搜索分析标签页 */}
|
||||
<TabsContent value="analytics" className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base">查询性能</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mb-2">{searchResults?.stats.queryTime || 0}ms</div>
|
||||
<Progress value={Math.min((searchResults?.stats.queryTime || 0) / 10, 100)} className="h-2" />
|
||||
<p className="text-xs text-muted-foreground mt-2">目标: <1000ms</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base">结果质量</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mb-2">
|
||||
{searchResults
|
||||
? Math.round(
|
||||
(searchResults.results.reduce((sum, r) => sum + r.relevanceScore, 0) /
|
||||
searchResults.results.length) *
|
||||
100,
|
||||
)
|
||||
: 0}
|
||||
%
|
||||
</div>
|
||||
<Progress
|
||||
value={
|
||||
searchResults
|
||||
? Math.round(
|
||||
(searchResults.results.reduce((sum, r) => sum + r.relevanceScore, 0) /
|
||||
searchResults.results.length) *
|
||||
100,
|
||||
)
|
||||
: 0
|
||||
}
|
||||
className="h-2"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-2">平均相关性评分</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base">AI增强率</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold mb-2">{useAI ? "100" : "0"}%</div>
|
||||
<Progress value={useAI ? 100 : 0} className="h-2" />
|
||||
<p className="text-xs text-muted-foreground mt-2">AI功能启用状态</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 搜索历史标签页 */}
|
||||
<TabsContent value="history" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Clock className="h-5 w-5" />
|
||||
搜索历史
|
||||
</CardTitle>
|
||||
<CardDescription>您最近的搜索记录</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{searchHistory.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{searchHistory.map((query, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50 cursor-pointer"
|
||||
onClick={() => {
|
||||
setSearchQuery(query)
|
||||
performSearch(query)
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Search className="h-4 w-4 text-muted-foreground" />
|
||||
<span>{query}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm">
|
||||
重新搜索
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Clock className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<p className="text-muted-foreground">暂无搜索历史</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
268
app/overview/dashboard/page.tsx
Normal file
268
app/overview/dashboard/page.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import {
|
||||
BarChart3,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Database,
|
||||
Activity,
|
||||
RefreshCw,
|
||||
Download,
|
||||
Eye,
|
||||
Zap,
|
||||
Target,
|
||||
AlertCircle,
|
||||
} from "lucide-react"
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [timeRange, setTimeRange] = useState("today")
|
||||
|
||||
const dashboardStats = {
|
||||
totalUsers: 125678,
|
||||
activeUsers: 45678,
|
||||
dataVolume: 1245678,
|
||||
aiAccuracy: 96.8,
|
||||
growthRate: 12.5,
|
||||
qualityScore: 94.2,
|
||||
}
|
||||
|
||||
const recentActivities = [
|
||||
{
|
||||
id: 1,
|
||||
type: "data_sync",
|
||||
message: "用户数据同步完成",
|
||||
time: "2分钟前",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "ai_analysis",
|
||||
message: "AI模型训练完成",
|
||||
time: "5分钟前",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "quality_check",
|
||||
message: "数据质量检查通过",
|
||||
time: "10分钟前",
|
||||
status: "warning",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">数据总览</h1>
|
||||
<p className="text-muted-foreground">全局数据资产概览与监控</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger className="w-[120px]">
|
||||
<SelectValue placeholder="时间范围" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="today">今日</SelectItem>
|
||||
<SelectItem value="week">本周</SelectItem>
|
||||
<SelectItem value="month">本月</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="icon">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
导出
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 核心指标卡片 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<Card className="bg-gradient-to-r from-blue-50 to-cyan-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">总用户数</CardTitle>
|
||||
<Users className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-blue-600">{dashboardStats.totalUsers.toLocaleString()}</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<TrendingUp className="h-3 w-3 text-green-500 mr-1" />
|
||||
<span className="text-xs text-green-600">+{dashboardStats.growthRate}%</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">活跃用户</CardTitle>
|
||||
<Activity className="h-4 w-4 text-green-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-green-600">{dashboardStats.activeUsers.toLocaleString()}</div>
|
||||
<div className="text-xs text-muted-foreground mt-2">
|
||||
活跃率 {((dashboardStats.activeUsers / dashboardStats.totalUsers) * 100).toFixed(1)}%
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-pink-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">数据量</CardTitle>
|
||||
<Database className="h-4 w-4 text-purple-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-purple-600">{dashboardStats.dataVolume.toLocaleString()}</div>
|
||||
<div className="text-xs text-muted-foreground mt-2">条记录</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-orange-50 to-red-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">AI准确率</CardTitle>
|
||||
<Target className="h-4 w-4 text-orange-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-orange-600">{dashboardStats.aiAccuracy}%</div>
|
||||
<div className="text-xs text-muted-foreground mt-2">模型置信度</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 详细分析 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* 数据质量监控 */}
|
||||
<Card className="lg:col-span-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-5 w-5 text-blue-600" />
|
||||
数据质量监控
|
||||
</CardTitle>
|
||||
<CardDescription>实时数据质量评估</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>整体质量评分</span>
|
||||
<span className="font-medium">{dashboardStats.qualityScore}%</span>
|
||||
</div>
|
||||
<Progress value={dashboardStats.qualityScore} className="h-2" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>数据完整性</span>
|
||||
<span className="font-medium">98.5%</span>
|
||||
</div>
|
||||
<Progress value={98.5} className="h-2" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>数据准确性</span>
|
||||
<span className="font-medium">96.2%</span>
|
||||
</div>
|
||||
<Progress value={96.2} className="h-2" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>数据一致性</span>
|
||||
<span className="font-medium">94.8%</span>
|
||||
</div>
|
||||
<Progress value={94.8} className="h-2" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 实时活动 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 text-green-600" />
|
||||
实时活动
|
||||
</CardTitle>
|
||||
<CardDescription>系统活动监控</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{recentActivities.map((activity) => (
|
||||
<div key={activity.id} className="flex items-start gap-3 p-2 hover:bg-gray-50 rounded-lg">
|
||||
<div
|
||||
className={`p-1 rounded-full ${
|
||||
activity.status === "success"
|
||||
? "text-green-600"
|
||||
: activity.status === "warning"
|
||||
? "text-yellow-600"
|
||||
: "text-red-600"
|
||||
}`}
|
||||
>
|
||||
{activity.status === "success" ? (
|
||||
<Zap className="h-4 w-4" />
|
||||
) : activity.status === "warning" ? (
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
) : (
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{activity.message}</p>
|
||||
<p className="text-xs text-gray-500">{activity.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button variant="outline" className="w-full mt-4 bg-transparent">
|
||||
查看全部活动
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 快速操作 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-600" />
|
||||
快速操作
|
||||
</CardTitle>
|
||||
<CardDescription>常用功能快捷入口</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Button variant="outline" className="h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Eye className="h-5 w-5" />
|
||||
<span className="text-xs">查看报告</span>
|
||||
</Button>
|
||||
<Button variant="outline" className="h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Database className="h-5 w-5" />
|
||||
<span className="text-xs">数据导入</span>
|
||||
</Button>
|
||||
<Button variant="outline" className="h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Users className="h-5 w-5" />
|
||||
<span className="text-xs">用户管理</span>
|
||||
</Button>
|
||||
<Button variant="outline" className="h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Target className="h-5 w-5" />
|
||||
<span className="text-xs">AI分析</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
283
app/overview/monitoring/page.tsx
Normal file
283
app/overview/monitoring/page.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import {
|
||||
Activity,
|
||||
Server,
|
||||
Database,
|
||||
Cpu,
|
||||
HardDrive,
|
||||
Network,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
RefreshCw,
|
||||
Bell,
|
||||
} from "lucide-react"
|
||||
|
||||
export default function MonitoringPage() {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [lastUpdate, setLastUpdate] = useState(new Date())
|
||||
|
||||
const systemMetrics = {
|
||||
cpu: 45.2,
|
||||
memory: 67.8,
|
||||
disk: 34.5,
|
||||
network: 23.1,
|
||||
}
|
||||
|
||||
const serviceStatus = [
|
||||
{ name: "数据接入服务", status: "running", uptime: "99.9%", responseTime: "120ms" },
|
||||
{ name: "AI分析引擎", status: "running", uptime: "99.7%", responseTime: "340ms" },
|
||||
{ name: "用户画像服务", status: "warning", uptime: "98.5%", responseTime: "580ms" },
|
||||
{ name: "数据存储服务", status: "running", uptime: "99.8%", responseTime: "45ms" },
|
||||
{ name: "搜索引擎", status: "running", uptime: "99.6%", responseTime: "89ms" },
|
||||
]
|
||||
|
||||
const alerts = [
|
||||
{
|
||||
id: 1,
|
||||
level: "warning",
|
||||
message: "用户画像服务响应时间超过阈值",
|
||||
time: "5分钟前",
|
||||
service: "用户画像服务",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
level: "info",
|
||||
message: "数据同步任务完成",
|
||||
time: "10分钟前",
|
||||
service: "数据接入服务",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
level: "success",
|
||||
message: "AI模型训练完成",
|
||||
time: "15分钟前",
|
||||
service: "AI分析引擎",
|
||||
},
|
||||
]
|
||||
|
||||
const handleRefresh = () => {
|
||||
setIsRefreshing(true)
|
||||
setTimeout(() => {
|
||||
setIsRefreshing(false)
|
||||
setLastUpdate(new Date())
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "running":
|
||||
return <CheckCircle className="h-4 w-4 text-green-500" />
|
||||
case "warning":
|
||||
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
|
||||
case "error":
|
||||
return <XCircle className="h-4 w-4 text-red-500" />
|
||||
default:
|
||||
return <CheckCircle className="h-4 w-4 text-gray-500" />
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "running":
|
||||
return "bg-green-100 text-green-800"
|
||||
case "warning":
|
||||
return "bg-yellow-100 text-yellow-800"
|
||||
case "error":
|
||||
return "bg-red-100 text-red-800"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800"
|
||||
}
|
||||
}
|
||||
|
||||
const getAlertIcon = (level: string) => {
|
||||
switch (level) {
|
||||
case "warning":
|
||||
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
|
||||
case "error":
|
||||
return <XCircle className="h-4 w-4 text-red-500" />
|
||||
case "success":
|
||||
return <CheckCircle className="h-4 w-4 text-green-500" />
|
||||
default:
|
||||
return <Bell className="h-4 w-4 text-blue-500" />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">实时监控</h1>
|
||||
<p className="text-muted-foreground">系统运行状态与性能监控</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">最后更新: {lastUpdate.toLocaleTimeString()}</span>
|
||||
<Button variant="outline" size="icon" onClick={handleRefresh} disabled={isRefreshing}>
|
||||
<RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 系统资源监控 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<Card className="bg-gradient-to-r from-blue-50 to-cyan-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">CPU使用率</CardTitle>
|
||||
<Cpu className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-blue-600">{systemMetrics.cpu}%</div>
|
||||
<Progress value={systemMetrics.cpu} className="mt-2 h-2" />
|
||||
<div className="text-xs text-muted-foreground mt-1">正常范围</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">内存使用率</CardTitle>
|
||||
<HardDrive className="h-4 w-4 text-green-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-green-600">{systemMetrics.memory}%</div>
|
||||
<Progress value={systemMetrics.memory} className="mt-2 h-2" />
|
||||
<div className="text-xs text-muted-foreground mt-1">正常范围</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-pink-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">磁盘使用率</CardTitle>
|
||||
<Database className="h-4 w-4 text-purple-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-purple-600">{systemMetrics.disk}%</div>
|
||||
<Progress value={systemMetrics.disk} className="mt-2 h-2" />
|
||||
<div className="text-xs text-muted-foreground mt-1">充足空间</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-r from-orange-50 to-red-50 border-none shadow-md">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm font-medium">网络流量</CardTitle>
|
||||
<Network className="h-4 w-4 text-orange-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-orange-600">{systemMetrics.network}%</div>
|
||||
<Progress value={systemMetrics.network} className="mt-2 h-2" />
|
||||
<div className="text-xs text-muted-foreground mt-1">流量正常</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 服务状态监控 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Server className="h-5 w-5 text-blue-600" />
|
||||
服务状态
|
||||
</CardTitle>
|
||||
<CardDescription>各服务模块运行状态</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{serviceStatus.map((service, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
{getStatusIcon(service.status)}
|
||||
<div>
|
||||
<p className="font-medium text-sm">{service.name}</p>
|
||||
<p className="text-xs text-muted-foreground">响应时间: {service.responseTime}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Badge className={getStatusColor(service.status)}>
|
||||
{service.status === "running" ? "运行中" : service.status === "warning" ? "警告" : "错误"}
|
||||
</Badge>
|
||||
<p className="text-xs text-muted-foreground mt-1">可用性: {service.uptime}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 告警信息 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Bell className="h-5 w-5 text-yellow-600" />
|
||||
告警信息
|
||||
</CardTitle>
|
||||
<CardDescription>系统告警与通知</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{alerts.map((alert) => (
|
||||
<div key={alert.id} className="flex items-start gap-3 p-3 border rounded-lg">
|
||||
{getAlertIcon(alert.level)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium">{alert.message}</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="text-xs text-muted-foreground">{alert.service}</span>
|
||||
<span className="text-xs text-muted-foreground">•</span>
|
||||
<span className="text-xs text-muted-foreground">{alert.time}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button variant="outline" className="w-full mt-4 bg-transparent">
|
||||
查看全部告警
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 性能趋势 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 text-green-600" />
|
||||
性能趋势
|
||||
</CardTitle>
|
||||
<CardDescription>系统性能历史趋势分析</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="text-center p-4 bg-blue-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-blue-600">99.8%</div>
|
||||
<div className="text-sm text-muted-foreground">系统可用性</div>
|
||||
<div className="text-xs text-green-600 mt-1">↑ 0.2%</div>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-green-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-green-600">156ms</div>
|
||||
<div className="text-sm text-muted-foreground">平均响应时间</div>
|
||||
<div className="text-xs text-green-600 mt-1">↓ 12ms</div>
|
||||
</div>
|
||||
<div className="text-center p-4 bg-purple-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">1.2M</div>
|
||||
<div className="text-sm text-muted-foreground">日处理请求</div>
|
||||
<div className="text-xs text-green-600 mt-1">↑ 15%</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
439
app/overview/search/page.tsx
Normal file
439
app/overview/search/page.tsx
Normal file
@@ -0,0 +1,439 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Avatar } from "@/components/ui/avatar"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import {
|
||||
Search,
|
||||
Filter,
|
||||
SortAsc,
|
||||
User,
|
||||
Tag,
|
||||
Database,
|
||||
BrainCircuit,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Calendar,
|
||||
TrendingUp,
|
||||
Eye,
|
||||
RefreshCw,
|
||||
} from "lucide-react"
|
||||
|
||||
export default function SearchPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [searchType, setSearchType] = useState("all")
|
||||
const [sortBy, setSortBy] = useState("relevance")
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
const [searchResults, setSearchResults] = useState<any[]>([])
|
||||
|
||||
// 模拟搜索数据
|
||||
const mockSearchData = [
|
||||
{
|
||||
id: "user_001",
|
||||
type: "user",
|
||||
name: "张明华",
|
||||
phone: "13800138000",
|
||||
email: "zhangsan@example.com",
|
||||
company: "科技创新有限公司",
|
||||
tags: ["高价值", "技术决策者", "活跃用户", "iOS用户"],
|
||||
aiInsights: ["科技爱好者", "决策影响者", "高消费潜力"],
|
||||
lastActive: "2小时前",
|
||||
location: "北京市",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
rfmScore: 95,
|
||||
sources: ["抖音", "微信", "表单提交"],
|
||||
joinDate: "2023-06-15",
|
||||
relevanceScore: 98,
|
||||
},
|
||||
{
|
||||
id: "user_002",
|
||||
type: "user",
|
||||
name: "李雨婷",
|
||||
phone: "13900139001",
|
||||
email: "lisi@example.com",
|
||||
company: "数字营销公司",
|
||||
tags: ["中高价值", "产品经理", "内容创作者"],
|
||||
aiInsights: ["创意思维", "社交活跃", "品牌敏感"],
|
||||
lastActive: "1天前",
|
||||
location: "上海市",
|
||||
avatar: "/placeholder.svg?height=40&width=40",
|
||||
rfmScore: 78,
|
||||
sources: ["小红书", "微信", "客服咨询"],
|
||||
joinDate: "2023-08-22",
|
||||
relevanceScore: 85,
|
||||
},
|
||||
{
|
||||
id: "tag_001",
|
||||
type: "tag",
|
||||
name: "高价值用户",
|
||||
description: "RFM评分超过80分的用户",
|
||||
userCount: 12456,
|
||||
category: "价值分类",
|
||||
createdDate: "2023-05-10",
|
||||
relevanceScore: 92,
|
||||
},
|
||||
{
|
||||
id: "insight_001",
|
||||
type: "insight",
|
||||
title: "用户行为模式分析",
|
||||
description: "基于AI分析的用户行为偏好洞察",
|
||||
confidence: 94.5,
|
||||
affectedUsers: 8765,
|
||||
createdDate: "2023-12-01",
|
||||
relevanceScore: 88,
|
||||
},
|
||||
]
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = async (query: string) => {
|
||||
if (!query.trim()) {
|
||||
setSearchResults([])
|
||||
return
|
||||
}
|
||||
|
||||
setIsSearching(true)
|
||||
|
||||
// 模拟搜索延迟
|
||||
setTimeout(() => {
|
||||
const filtered = mockSearchData.filter((item) => {
|
||||
const searchText = query.toLowerCase()
|
||||
|
||||
if (searchType !== "all" && item.type !== searchType) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 根据不同类型进行搜索
|
||||
switch (item.type) {
|
||||
case "user":
|
||||
return (
|
||||
item.name.toLowerCase().includes(searchText) ||
|
||||
item.phone.includes(searchText) ||
|
||||
item.email.toLowerCase().includes(searchText) ||
|
||||
item.company.toLowerCase().includes(searchText) ||
|
||||
item.tags.some((tag: string) => tag.toLowerCase().includes(searchText)) ||
|
||||
item.aiInsights.some((insight: string) => insight.toLowerCase().includes(searchText)) ||
|
||||
item.location.toLowerCase().includes(searchText)
|
||||
)
|
||||
case "tag":
|
||||
return (
|
||||
item.name.toLowerCase().includes(searchText) ||
|
||||
item.description.toLowerCase().includes(searchText) ||
|
||||
item.category.toLowerCase().includes(searchText)
|
||||
)
|
||||
case "insight":
|
||||
return item.title.toLowerCase().includes(searchText) || item.description.toLowerCase().includes(searchText)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// 排序
|
||||
if (sortBy === "relevance") {
|
||||
filtered.sort((a, b) => b.relevanceScore - a.relevanceScore)
|
||||
} else if (sortBy === "date") {
|
||||
filtered.sort(
|
||||
(a, b) => new Date(b.createdDate || b.joinDate).getTime() - new Date(a.createdDate || a.joinDate).getTime(),
|
||||
)
|
||||
} else if (sortBy === "name") {
|
||||
filtered.sort((a, b) => (a.name || a.title).localeCompare(b.name || b.title))
|
||||
}
|
||||
|
||||
setSearchResults(filtered)
|
||||
setIsSearching(false)
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// 搜索输入处理
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (searchQuery) {
|
||||
handleSearch(searchQuery)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
}, [searchQuery, searchType, sortBy])
|
||||
|
||||
const getTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "user":
|
||||
return <User className="h-4 w-4 text-blue-500" />
|
||||
case "tag":
|
||||
return <Tag className="h-4 w-4 text-green-500" />
|
||||
case "insight":
|
||||
return <BrainCircuit className="h-4 w-4 text-purple-500" />
|
||||
default:
|
||||
return <Database className="h-4 w-4 text-gray-500" />
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeBadgeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case "user":
|
||||
return "bg-blue-100 text-blue-700"
|
||||
case "tag":
|
||||
return "bg-green-100 text-green-700"
|
||||
case "insight":
|
||||
return "bg-purple-100 text-purple-700"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-700"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">智能搜索</h1>
|
||||
<p className="text-muted-foreground">AI驱动的全局智能搜索引擎</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索栏 */}
|
||||
<Card className="bg-gradient-to-r from-purple-50 to-blue-50 border-none shadow-lg">
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
{/* 主搜索框 */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索用户、标签、洞察或任何相关信息..."
|
||||
className="pl-10 h-12 text-base border-2 border-purple-200 focus:border-purple-400"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
{isSearching && (
|
||||
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||
<RefreshCw className="h-5 w-5 animate-spin text-purple-500" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 搜索选项 */}
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter className="h-4 w-4 text-muted-foreground" />
|
||||
<Select value={searchType} onValueChange={setSearchType}>
|
||||
<SelectTrigger className="w-[120px]">
|
||||
<SelectValue placeholder="类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="user">用户</SelectItem>
|
||||
<SelectItem value="tag">标签</SelectItem>
|
||||
<SelectItem value="insight">洞察</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<SortAsc className="h-4 w-4 text-muted-foreground" />
|
||||
<Select value={sortBy} onValueChange={setSortBy}>
|
||||
<SelectTrigger className="w-[120px]">
|
||||
<SelectValue placeholder="排序" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="relevance">相关性</SelectItem>
|
||||
<SelectItem value="date">时间</SelectItem>
|
||||
<SelectItem value="name">名称</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 搜索结果 */}
|
||||
{searchQuery && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>搜索结果</span>
|
||||
<Badge variant="outline">{searchResults.length} 个结果</Badge>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
搜索关键词: "{searchQuery}"{searchType !== "all" && ` · 类型: ${searchType}`}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{searchResults.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{searchResults.map((result) => (
|
||||
<div
|
||||
key={result.id}
|
||||
className="flex items-start gap-4 p-4 border rounded-lg hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
>
|
||||
{/* 类型图标 */}
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
{result.type === "user" ? (
|
||||
<Avatar className="h-10 w-10">
|
||||
<img src={result.avatar || "/placeholder.svg"} alt={result.name} />
|
||||
</Avatar>
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
|
||||
{getTypeIcon(result.type)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h3 className="font-semibold text-lg truncate">{result.name || result.title}</h3>
|
||||
<Badge className={getTypeBadgeColor(result.type)}>
|
||||
{result.type === "user" ? "用户" : result.type === "tag" ? "标签" : "洞察"}
|
||||
</Badge>
|
||||
{result.relevanceScore && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
相关性: {result.relevanceScore}%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 用户信息 */}
|
||||
{result.type === "user" && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<Phone className="h-3 w-3" />
|
||||
{result.phone}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Mail className="h-3 w-3" />
|
||||
{result.email}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<MapPin className="h-3 w-3" />
|
||||
{result.location}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">公司:</span>
|
||||
<span className="text-sm">{result.company}</span>
|
||||
<Badge className="bg-orange-100 text-orange-700 text-xs">RFM: {result.rfmScore}</Badge>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{result.aiInsights.slice(0, 3).map((insight: string, index: number) => (
|
||||
<Badge key={index} className="text-xs bg-purple-100 text-purple-700">
|
||||
{insight}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 标签信息 */}
|
||||
{result.type === "tag" && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">{result.description}</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="flex items-center gap-1">
|
||||
<User className="h-3 w-3" />
|
||||
{result.userCount.toLocaleString()} 用户
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
{result.category}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{result.createdDate}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 洞察信息 */}
|
||||
{result.type === "insight" && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">{result.description}</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className="flex items-center gap-1">
|
||||
<TrendingUp className="h-3 w-3" />
|
||||
置信度: {result.confidence}%
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<User className="h-3 w-3" />
|
||||
影响用户: {result.affectedUsers.toLocaleString()}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{result.createdDate}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex-shrink-0">
|
||||
<Button variant="outline" size="sm">
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
查看
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-medium mb-2">未找到相关结果</h3>
|
||||
<p className="text-muted-foreground">尝试使用其他关键词或调整搜索条件</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 搜索建议 */}
|
||||
{!searchQuery && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BrainCircuit className="h-5 w-5 text-purple-600" />
|
||||
搜索建议
|
||||
</CardTitle>
|
||||
<CardDescription>热门搜索和智能推荐</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">热门搜索</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{["高价值用户", "技术决策者", "活跃用户", "流失风险", "新用户"].map((term) => (
|
||||
<Button
|
||||
key={term}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSearchQuery(term)}
|
||||
className="text-xs"
|
||||
>
|
||||
{term}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">智能推荐</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-muted-foreground">• 搜索特定用户: 输入姓名、手机号或邮箱</div>
|
||||
<div className="text-sm text-muted-foreground">• 查找标签: 输入标签名称或描述</div>
|
||||
<div className="text-sm text-muted-foreground">• 发现洞察: 输入行为模式或分析结果</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
635
app/page.tsx
635
app/page.tsx
@@ -1,408 +1,335 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } 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 { TooltipHelp, TooltipProvider } from "@/components/ui/tooltip-help"
|
||||
import {
|
||||
Database,
|
||||
Users,
|
||||
Target,
|
||||
TrendingUp,
|
||||
RefreshCw,
|
||||
Download,
|
||||
ChevronRight,
|
||||
Activity,
|
||||
TrendingUp,
|
||||
Database,
|
||||
Search,
|
||||
RefreshCw,
|
||||
Zap,
|
||||
BarChart3,
|
||||
Target,
|
||||
Globe,
|
||||
UserCheck,
|
||||
Layers,
|
||||
Tag,
|
||||
Clock,
|
||||
} from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function HomePage() {
|
||||
const [timeRange, setTimeRange] = useState("today")
|
||||
export default function OverviewPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [lastUpdate, setLastUpdate] = useState(new Date())
|
||||
|
||||
// 今日数据概览
|
||||
const todayStats = {
|
||||
dataSync: {
|
||||
total: 1245678,
|
||||
growth: "+12.5%",
|
||||
sources: 8,
|
||||
lastUpdate: "2分钟前",
|
||||
},
|
||||
userPool: {
|
||||
totalUsers: 125678,
|
||||
activeUsers: 45678,
|
||||
newUsers: 1234,
|
||||
growth: "+8.2%",
|
||||
},
|
||||
userPortrait: {
|
||||
totalTags: 342,
|
||||
activeTags: 287,
|
||||
coverage: "95.8%",
|
||||
growth: "+5.3%",
|
||||
},
|
||||
userValuation: {
|
||||
avgValue: 3248,
|
||||
highValue: 12456,
|
||||
growth: "+15.8%",
|
||||
upgradeRate: "12.5%",
|
||||
},
|
||||
// 核心数据状态
|
||||
const [coreData, setCoreData] = useState({
|
||||
totalUsers: 4000000000, // 40亿用户
|
||||
activeUsers: 2800000000, // 28亿活跃用户
|
||||
dataGrowthRate: 15.8, // 数据增长率
|
||||
assetGrowthRate: 23.5, // 资产增长率
|
||||
realTimeQueries: 156789, // 实时查询数
|
||||
dataVolume: "12.8TB", // 数据量
|
||||
systemHealth: 99.2, // 系统健康度
|
||||
aiAnalysis: 8456, // AI分析次数
|
||||
})
|
||||
|
||||
// 实时数据更新
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCoreData((prev) => ({
|
||||
...prev,
|
||||
realTimeQueries: prev.realTimeQueries + Math.floor(Math.random() * 50) + 10,
|
||||
activeUsers: prev.activeUsers + Math.floor(Math.random() * 1000) + 100,
|
||||
}))
|
||||
setLastUpdate(new Date())
|
||||
}, 5000) // 每5秒更新一次
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
// 手动刷新
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true)
|
||||
// 模拟数据刷新
|
||||
setTimeout(() => {
|
||||
setCoreData((prev) => ({
|
||||
...prev,
|
||||
totalUsers: prev.totalUsers + Math.floor(Math.random() * 10000) + 1000,
|
||||
dataGrowthRate: +(Math.random() * 5 + 12).toFixed(1),
|
||||
assetGrowthRate: +(Math.random() * 8 + 18).toFixed(1),
|
||||
}))
|
||||
setLastUpdate(new Date())
|
||||
setIsRefreshing(false)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 实时活动数据
|
||||
const realtimeActivities = [
|
||||
{
|
||||
id: 1,
|
||||
type: "data_sync",
|
||||
message: "IMEI数据同步完成",
|
||||
time: "刚刚",
|
||||
status: "success",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "user_pool",
|
||||
message: "新增用户池分群",
|
||||
time: "2分钟前",
|
||||
status: "info",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "user_portrait",
|
||||
message: "标签规则自动执行",
|
||||
time: "5分钟前",
|
||||
status: "warning",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: "user_valuation",
|
||||
message: "用户价值模型更新完成",
|
||||
time: "8分钟前",
|
||||
status: "success",
|
||||
},
|
||||
]
|
||||
|
||||
const getActivityIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "data_sync":
|
||||
return <Database className="h-4 w-4" />
|
||||
case "user_pool":
|
||||
return <Users className="h-4 w-4" />
|
||||
case "user_portrait":
|
||||
return <Target className="h-4 w-4" />
|
||||
case "user_valuation":
|
||||
return <TrendingUp className="h-4 w-4" />
|
||||
default:
|
||||
return <Activity className="h-4 w-4" />
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
if (searchQuery.trim()) {
|
||||
// 跳转到智能搜索页面
|
||||
window.location.href = `/intelligent-search?q=${encodeURIComponent(searchQuery)}`
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "success":
|
||||
return "text-green-600"
|
||||
case "warning":
|
||||
return "text-yellow-600"
|
||||
case "info":
|
||||
return "text-blue-600"
|
||||
default:
|
||||
return "text-gray-600"
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 1000000000) {
|
||||
return (num / 1000000000).toFixed(1) + "B"
|
||||
}
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + "M"
|
||||
}
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + "K"
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-4 md:py-6 space-y-4 md:space-y-6">
|
||||
{/* 页面标题和工具栏 */}
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">数据概览</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground">卡若数据资产中台总览</p>
|
||||
<TooltipProvider>
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
{/* 页面标题和搜索 */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">概览</h1>
|
||||
<p className="text-muted-foreground mt-1 flex items-center gap-2">
|
||||
数据资产中台总览
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
{lastUpdate.toLocaleTimeString()}
|
||||
</Badge>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索用户或流量关键词..."
|
||||
className="pl-10 pr-20"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyPress={(e) => e.key === "Enter" && handleSearch()}
|
||||
/>
|
||||
<Button size="sm" className="absolute right-1 top-1/2 transform -translate-y-1/2" onClick={handleSearch}>
|
||||
搜索
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={isRefreshing}>
|
||||
<RefreshCw className={`mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
|
||||
{isRefreshing ? "刷新中" : "刷新"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger className="w-[120px] text-sm">
|
||||
<SelectValue placeholder="时间范围" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="today">今日</SelectItem>
|
||||
<SelectItem value="week">本周</SelectItem>
|
||||
<SelectItem value="month">本月</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="icon" className="h-9 w-9 bg-transparent">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="hidden md:flex bg-transparent">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
导出
|
||||
</Button>
|
||||
|
||||
{/* 核心用户数据展示 - 40亿用户为中心 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* 中心用户总数卡片 */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="h-full border-2 border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
<CardHeader className="text-center pb-2">
|
||||
<CardTitle className="text-lg flex items-center justify-center gap-2">
|
||||
<Globe className="h-6 w-6 text-blue-600" />
|
||||
用户总数
|
||||
<TooltipHelp content="平台累计用户总数,包括所有注册和识别的用户" />
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center">
|
||||
<div className="text-5xl font-bold text-blue-600 mb-2">{formatNumber(coreData.totalUsers)}</div>
|
||||
<div className="text-sm text-muted-foreground">全球用户覆盖</div>
|
||||
<div className="mt-4 grid grid-cols-2 gap-2 text-xs">
|
||||
<div className="bg-white/50 rounded p-2">
|
||||
<div className="font-medium">活跃用户</div>
|
||||
<div className="text-green-600 font-bold">{formatNumber(coreData.activeUsers)}</div>
|
||||
</div>
|
||||
<div className="bg-white/50 rounded p-2">
|
||||
<div className="font-medium">活跃率</div>
|
||||
<div className="text-blue-600 font-bold">
|
||||
{((coreData.activeUsers / coreData.totalUsers) * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 右侧数据指标 */}
|
||||
<div className="lg:col-span-2 grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-1">
|
||||
用户资产增长
|
||||
<TooltipHelp content="用户资产价值的增长率,反映用户价值提升情况" />
|
||||
</CardTitle>
|
||||
<TrendingUp className="h-4 w-4 text-green-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-green-600">+{coreData.assetGrowthRate}%</div>
|
||||
<p className="text-xs text-muted-foreground">月度增长率</p>
|
||||
<div className="mt-2 h-2 bg-gray-200 rounded-full">
|
||||
<div
|
||||
className="h-2 bg-green-500 rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(coreData.assetGrowthRate * 2, 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-1">
|
||||
数据增长
|
||||
<TooltipHelp content="数据量的增长速度,包括用户行为数据、交易数据等" />
|
||||
</CardTitle>
|
||||
<Database className="h-4 w-4 text-blue-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-blue-600">+{coreData.dataGrowthRate}%</div>
|
||||
<p className="text-xs text-muted-foreground">数据量: {coreData.dataVolume}</p>
|
||||
<div className="mt-2 h-2 bg-gray-200 rounded-full">
|
||||
<div
|
||||
className="h-2 bg-blue-500 rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(coreData.dataGrowthRate * 3, 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-1">
|
||||
实时查询
|
||||
<TooltipHelp content="当前实时查询次数,反映系统活跃度" />
|
||||
</CardTitle>
|
||||
<Zap className="h-4 w-4 text-yellow-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-yellow-600">{formatNumber(coreData.realTimeQueries)}</div>
|
||||
<p className="text-xs text-muted-foreground">实时查询次数</p>
|
||||
<div className="flex items-center mt-2">
|
||||
<div className="w-2 h-2 bg-yellow-500 rounded-full animate-pulse mr-2"></div>
|
||||
<span className="text-xs text-green-600">实时更新中</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-1">
|
||||
系统状态
|
||||
<TooltipHelp content="系统整体健康状态和AI分析能力" />
|
||||
</CardTitle>
|
||||
<Activity className="h-4 w-4 text-purple-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-purple-600">{coreData.systemHealth}%</div>
|
||||
<p className="text-xs text-muted-foreground">AI分析: {formatNumber(coreData.aiAnalysis)} 次</p>
|
||||
<div className="flex items-center mt-2">
|
||||
<Badge className="bg-green-100 text-green-800 text-xs">系统正常</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 核心功能模块 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* 数据中台 */}
|
||||
<Link href="/data-platform">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-5 w-5 text-blue-600" />
|
||||
<CardTitle className="text-base">数据中台</CardTitle>
|
||||
</div>
|
||||
<Badge className="bg-blue-100 text-blue-700">核心</Badge>
|
||||
</div>
|
||||
<CardDescription className="text-xs">多源数据整合中心</CardDescription>
|
||||
{/* 数据分布概览 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">高价值用户</CardTitle>
|
||||
<Target className="h-4 w-4 text-red-500" />
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">数据总量</span>
|
||||
<span className="font-bold">{todayStats.dataSync.total.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">数据源</span>
|
||||
<span className="font-bold">{todayStats.dataSync.sources}个</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">增长率</span>
|
||||
<span className="text-green-600 font-bold">{todayStats.dataSync.growth}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-gray-500">最后更新: {todayStats.dataSync.lastUpdate}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">2.8M</div>
|
||||
<p className="text-xs text-muted-foreground">占比 7.2%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户池 */}
|
||||
<Link href="/user-pool">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5 text-green-600" />
|
||||
<CardTitle className="text-base">用户池</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户数据管理中心</CardDescription>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">活跃用户</CardTitle>
|
||||
<UserCheck className="h-4 w-4 text-green-500" />
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">总用户数</span>
|
||||
<span className="font-bold">{todayStats.userPool.totalUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">活跃用户</span>
|
||||
<span className="font-bold">{todayStats.userPool.activeUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">新增用户</span>
|
||||
<span className="font-bold">{todayStats.userPool.newUsers.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-green-600">增长 {todayStats.userPool.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-green-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">28.5M</div>
|
||||
<p className="text-xs text-muted-foreground">日活跃用户</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户画像 */}
|
||||
<Link href="/user-portrait">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-5 w-5 text-purple-600" />
|
||||
<CardTitle className="text-base">用户画像</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户标签画像分析</CardDescription>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">流量关键词</CardTitle>
|
||||
<BarChart3 className="h-4 w-4 text-blue-500" />
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">标签总数</span>
|
||||
<span className="font-bold">{todayStats.userPortrait.totalTags}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">活跃标签</span>
|
||||
<span className="font-bold">{todayStats.userPortrait.activeTags}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">覆盖率</span>
|
||||
<span className="text-purple-600 font-bold">{todayStats.userPortrait.coverage}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-purple-600">增长 {todayStats.userPortrait.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-purple-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">156K</div>
|
||||
<p className="text-xs text-muted-foreground">热门关键词数量</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
{/* 用户估值 */}
|
||||
<Link href="/user-valuation">
|
||||
<Card className="border-none shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer group">
|
||||
<CardHeader className="bg-gradient-to-r from-orange-50 to-red-50 pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5 text-orange-600" />
|
||||
<CardTitle className="text-base">用户估值</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription className="text-xs">用户价值评估模型</CardDescription>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">数据源</CardTitle>
|
||||
<Database className="h-4 w-4 text-purple-500" />
|
||||
</CardHeader>
|
||||
<CardContent className="pt-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">平均估值</span>
|
||||
<span className="font-bold">¥{todayStats.userValuation.avgValue.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">高价值用户</span>
|
||||
<span className="font-bold">{todayStats.userValuation.highValue.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">升级率</span>
|
||||
<span className="text-orange-600 font-bold">{todayStats.userValuation.upgradeRate}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<span className="text-xs text-orange-600">增长 {todayStats.userValuation.growth}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400 group-hover:text-orange-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">28</div>
|
||||
<p className="text-xs text-muted-foreground">已接入数据源</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 实时数据流 */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-6">
|
||||
{/* 今日关键指标 */}
|
||||
<Card className="lg:col-span-2 border-none shadow-md">
|
||||
{/* 实时活动流 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 text-blue-600" />
|
||||
今日关键指标
|
||||
<Activity className="h-5 w-5" />
|
||||
实时数据流
|
||||
<TooltipHelp content="系统实时数据处理和分析活动" />
|
||||
</CardTitle>
|
||||
<CardDescription>实时数据监控</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center p-3 bg-blue-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-blue-600">98.5%</div>
|
||||
<div className="text-xs text-gray-600">系统可用性</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-green-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-green-600">2.3s</div>
|
||||
<div className="text-xs text-gray-600">平均响应时间</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-purple-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">342</div>
|
||||
<div className="text-xs text-gray-600">活跃标签数</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-orange-50 rounded-lg">
|
||||
<div className="text-2xl font-bold text-orange-600">95.8%</div>
|
||||
<div className="text-xs text-gray-600">标签覆盖率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>数据处理进度</span>
|
||||
<span>87%</span>
|
||||
</div>
|
||||
<Progress value={87} className="h-2" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>标签规则执行</span>
|
||||
<span>92%</span>
|
||||
</div>
|
||||
<Progress value={92} className="h-2" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 实时活动 */}
|
||||
<Card className="border-none shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5 text-green-600" />
|
||||
实时活动
|
||||
</CardTitle>
|
||||
<CardDescription>系统活动监控</CardDescription>
|
||||
<CardDescription>实时监控数据处理和用户活动</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{realtimeActivities.map((activity) => (
|
||||
<div key={activity.id} className="flex items-start gap-3 p-2 hover:bg-gray-50 rounded-lg">
|
||||
<div className={`p-1 rounded-full ${getStatusColor(activity.status)}`}>
|
||||
{getActivityIcon(activity.type)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium truncate">{activity.message}</p>
|
||||
<p className="text-xs text-gray-500">{activity.time}</p>
|
||||
<div className="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<div className="font-medium">用户数据同步</div>
|
||||
<div className="text-sm text-muted-foreground">新增 1,247 个用户记录</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Badge className="bg-green-100 text-green-800">实时</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<div className="font-medium">AI分析完成</div>
|
||||
<div className="text-sm text-muted-foreground">用户画像分析处理完成</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className="bg-blue-100 text-blue-800">2分钟前</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-3 bg-purple-50 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-2 h-2 bg-purple-500 rounded-full animate-pulse"></div>
|
||||
<div>
|
||||
<div className="font-medium">流量关键词更新</div>
|
||||
<div className="text-sm text-muted-foreground">发现 89 个新热门关键词</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge className="bg-purple-100 text-purple-800">5分钟前</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full mt-4 text-sm bg-transparent">
|
||||
查看全部活动
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 快速操作 */}
|
||||
<Card className="border-none shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-600" />
|
||||
快速操作
|
||||
</CardTitle>
|
||||
<CardDescription>常用功能快捷入口</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Link href="/data-platform">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Database className="h-5 w-5" />
|
||||
<span className="text-xs">数据导入</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-pool/management">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<UserCheck className="h-5 w-5" />
|
||||
<span className="text-xs">用户管理</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-pool/segmentation">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Layers className="h-5 w-5" />
|
||||
<span className="text-xs">用户分群</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/user-portrait/tags">
|
||||
<Button variant="outline" className="w-full h-16 flex flex-col gap-1 bg-transparent">
|
||||
<Tag className="h-5 w-5" />
|
||||
<span className="text-xs">标签管理</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Search, Plus, Filter, Download, Tag, Edit, Trash2, MoreHorizontal } from "lucide-react"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { TagCategoryChart } from "@/components/tag-management/tag-category-chart"
|
||||
import { TagUsageStats } from "@/components/tag-management/tag-usage-stats"
|
||||
import { TagRelationshipGraph } from "@/components/tag-management/tag-relationship-graph"
|
||||
|
||||
interface TagItem {
|
||||
id: string
|
||||
name: string
|
||||
category: string
|
||||
type: "system" | "custom" | "derived"
|
||||
source: string
|
||||
coverage: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
const mockTags: TagItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "高价值用户",
|
||||
category: "用户价值",
|
||||
type: "derived",
|
||||
source: "RFM模型",
|
||||
coverage: 15.3,
|
||||
createdAt: "2023-05-12",
|
||||
updatedAt: "2023-07-15",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "90后",
|
||||
category: "人口属性",
|
||||
type: "system",
|
||||
source: "注册信息",
|
||||
coverage: 42.7,
|
||||
createdAt: "2023-01-05",
|
||||
updatedAt: "2023-01-05",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "游戏爱好者",
|
||||
category: "兴趣爱好",
|
||||
type: "custom",
|
||||
source: "行为分析",
|
||||
coverage: 28.4,
|
||||
createdAt: "2023-04-18",
|
||||
updatedAt: "2023-06-22",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "流失风险高",
|
||||
category: "流失风险",
|
||||
type: "derived",
|
||||
source: "预测模型",
|
||||
coverage: 8.9,
|
||||
createdAt: "2023-06-30",
|
||||
updatedAt: "2023-07-20",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "北京地区",
|
||||
category: "地理位置",
|
||||
type: "system",
|
||||
source: "IP分析",
|
||||
coverage: 12.5,
|
||||
createdAt: "2023-02-15",
|
||||
updatedAt: "2023-02-15",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
name: "周末活跃",
|
||||
category: "活跃时间",
|
||||
type: "custom",
|
||||
source: "行为分析",
|
||||
coverage: 35.2,
|
||||
createdAt: "2023-05-28",
|
||||
updatedAt: "2023-07-10",
|
||||
},
|
||||
]
|
||||
|
||||
const tagCategories = ["用户价值", "人口属性", "兴趣爱好", "流失风险", "地理位置", "活跃时间", "消费能力", "渠道来源"]
|
||||
|
||||
export default function TagManagementPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([])
|
||||
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
|
||||
const [newTag, setNewTag] = useState({
|
||||
name: "",
|
||||
category: "",
|
||||
type: "custom",
|
||||
source: "",
|
||||
})
|
||||
|
||||
const handleSelectAllTags = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedTags(mockTags.map((tag) => tag.id))
|
||||
} else {
|
||||
setSelectedTags([])
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectTag = (tagId: string, checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedTags([...selectedTags, tagId])
|
||||
} else {
|
||||
setSelectedTags(selectedTags.filter((id) => id !== tagId))
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateTag = () => {
|
||||
// 这里应该是创建标签的逻辑
|
||||
console.log("创建标签:", newTag)
|
||||
setIsCreateDialogOpen(false)
|
||||
setNewTag({
|
||||
name: "",
|
||||
category: "",
|
||||
type: "custom",
|
||||
source: "",
|
||||
})
|
||||
}
|
||||
|
||||
const filteredTags = mockTags.filter(
|
||||
(tag) =>
|
||||
tag.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
tag.category.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">标签管理</h1>
|
||||
<p className="text-muted-foreground mt-1">管理和组织用户标签体系</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
导出标签
|
||||
</Button>
|
||||
<Button onClick={() => setIsCreateDialogOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
创建标签
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">标签总数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<Tag className="h-5 w-5 text-blue-500 mr-2" />
|
||||
<div className="text-2xl font-bold">128</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">较上月增加 12 个</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">标签覆盖率</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">87.5%</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">较上月提升 2.3%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">标签使用率</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">76.2%</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">较上月提升 5.1%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="list" className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<TabsList>
|
||||
<TabsTrigger value="list">标签列表</TabsTrigger>
|
||||
<TabsTrigger value="analytics">标签分析</TabsTrigger>
|
||||
<TabsTrigger value="relationships">标签关系</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex space-x-2">
|
||||
<div className="relative w-64">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索标签..."
|
||||
className="pl-8"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="list" className="space-y-4">
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-12">
|
||||
<Checkbox
|
||||
checked={selectedTags.length === mockTags.length}
|
||||
onCheckedChange={handleSelectAllTags}
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead>标签名称</TableHead>
|
||||
<TableHead>分类</TableHead>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>数据来源</TableHead>
|
||||
<TableHead>覆盖率</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>更新时间</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredTags.map((tag) => (
|
||||
<TableRow key={tag.id}>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selectedTags.includes(tag.id)}
|
||||
onCheckedChange={(checked) => handleSelectTag(tag.id, !!checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{tag.name}</TableCell>
|
||||
<TableCell>{tag.category}</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={
|
||||
tag.type === "system"
|
||||
? "bg-blue-50 text-blue-700 border-blue-200"
|
||||
: tag.type === "derived"
|
||||
? "bg-purple-50 text-purple-700 border-purple-200"
|
||||
: "bg-green-50 text-green-700 border-green-200"
|
||||
}
|
||||
>
|
||||
{tag.type === "system" ? "系统" : tag.type === "derived" ? "衍生" : "自定义"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{tag.source}</TableCell>
|
||||
<TableCell>{tag.coverage}%</TableCell>
|
||||
<TableCell>{tag.createdAt}</TableCell>
|
||||
<TableCell>{tag.updatedAt}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="analytics" className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>标签分类分布</CardTitle>
|
||||
<CardDescription>按分类统计标签数量</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TagCategoryChart />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>标签使用统计</CardTitle>
|
||||
<CardDescription>标签在各系统中的使用情况</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TagUsageStats />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="relationships" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>标签关系图谱</CardTitle>
|
||||
<CardDescription>展示标签之间的关联关系</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="h-[500px]">
|
||||
<TagRelationshipGraph />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* 创建标签对话框 */}
|
||||
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建新标签</DialogTitle>
|
||||
<DialogDescription>添加新的用户标签到标签体系中</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="tag-name" className="text-right">
|
||||
标签名称
|
||||
</Label>
|
||||
<Input
|
||||
id="tag-name"
|
||||
value={newTag.name}
|
||||
onChange={(e) => setNewTag({ ...newTag, name: e.target.value })}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="tag-category" className="text-right">
|
||||
标签分类
|
||||
</Label>
|
||||
<Select value={newTag.category} onValueChange={(value) => setNewTag({ ...newTag, category: value })}>
|
||||
<SelectTrigger className="col-span-3">
|
||||
<SelectValue placeholder="选择标签分类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tagCategories.map((category) => (
|
||||
<SelectItem key={category} value={category}>
|
||||
{category}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="tag-type" className="text-right">
|
||||
标签类型
|
||||
</Label>
|
||||
<Select
|
||||
value={newTag.type}
|
||||
onValueChange={(value: "system" | "custom" | "derived") => setNewTag({ ...newTag, type: value })}
|
||||
>
|
||||
<SelectTrigger className="col-span-3">
|
||||
<SelectValue placeholder="选择标签类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">系统标签</SelectItem>
|
||||
<SelectItem value="custom">自定义标签</SelectItem>
|
||||
<SelectItem value="derived">衍生标签</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="tag-source" className="text-right">
|
||||
数据来源
|
||||
</Label>
|
||||
<Input
|
||||
id="tag-source"
|
||||
value={newTag.source}
|
||||
onChange={(e) => setNewTag({ ...newTag, source: e.target.value })}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleCreateTag}>创建</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Search, Plus, Filter, Play, Pause, Edit, Trash2, MoreHorizontal, FileText, Clock } from "lucide-react"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { RuleEditor } from "@/components/tag-rules/rule-editor"
|
||||
import { RuleExecutionHistory } from "@/components/tag-rules/rule-execution-history"
|
||||
|
||||
interface TagRule {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
targetTag: string
|
||||
condition: string
|
||||
status: "active" | "inactive" | "draft"
|
||||
priority: number
|
||||
createdAt: string
|
||||
lastRun: string
|
||||
affectedUsers: number
|
||||
}
|
||||
|
||||
const mockRules: TagRule[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "高价值用户识别",
|
||||
description: "根据用户消费金额和频次识别高价值用户",
|
||||
targetTag: "高价值用户",
|
||||
condition: "消费金额 > 5000 AND 消费频次 > 10",
|
||||
status: "active",
|
||||
priority: 1,
|
||||
createdAt: "2023-05-12",
|
||||
lastRun: "2023-07-21 15:30",
|
||||
affectedUsers: 1250,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "游戏爱好者标记",
|
||||
description: "根据用户浏览和购买行为识别游戏爱好者",
|
||||
targetTag: "游戏爱好者",
|
||||
condition: "游戏类目浏览时长 > 30min OR 游戏类目购买次数 > 2",
|
||||
status: "active",
|
||||
priority: 2,
|
||||
createdAt: "2023-04-18",
|
||||
lastRun: "2023-07-21 14:45",
|
||||
affectedUsers: 2840,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "流失风险预警",
|
||||
description: "识别30天内可能流失的用户",
|
||||
targetTag: "流失风险高",
|
||||
condition: "最近登录时间 > 15天 AND 最近30天活跃度下降率 > 50%",
|
||||
status: "active",
|
||||
priority: 1,
|
||||
createdAt: "2023-06-30",
|
||||
lastRun: "2023-07-21 13:20",
|
||||
affectedUsers: 890,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
name: "周末活跃用户",
|
||||
description: "识别周末活跃度高的用户",
|
||||
targetTag: "周末活跃",
|
||||
condition: "周末活跃时长 > 工作日活跃时长 * 1.5",
|
||||
status: "inactive",
|
||||
priority: 3,
|
||||
createdAt: "2023-05-28",
|
||||
lastRun: "2023-07-10 09:15",
|
||||
affectedUsers: 3520,
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
name: "潜在高转化用户",
|
||||
description: "识别浏览量高但未转化的潜在用户",
|
||||
targetTag: "潜在高转化",
|
||||
condition: "浏览商品数 > 20 AND 加购次数 > 5 AND 购买次数 = 0",
|
||||
status: "draft",
|
||||
priority: 2,
|
||||
createdAt: "2023-07-15",
|
||||
lastRun: "-",
|
||||
affectedUsers: 0,
|
||||
},
|
||||
]
|
||||
|
||||
export default function TagRulesPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
|
||||
const [newRule, setNewRule] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
targetTag: "",
|
||||
condition: "",
|
||||
priority: 2,
|
||||
status: "draft",
|
||||
})
|
||||
const [selectedRuleId, setSelectedRuleId] = useState<string | null>(null)
|
||||
|
||||
const handleCreateRule = () => {
|
||||
// 这里应该是创建规则的逻辑
|
||||
console.log("创建规则:", newRule)
|
||||
setIsCreateDialogOpen(false)
|
||||
setNewRule({
|
||||
name: "",
|
||||
description: "",
|
||||
targetTag: "",
|
||||
condition: "",
|
||||
priority: 2,
|
||||
status: "draft",
|
||||
})
|
||||
}
|
||||
|
||||
const filteredRules = mockRules.filter(
|
||||
(rule) =>
|
||||
rule.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
rule.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
rule.targetTag.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
)
|
||||
|
||||
const getStatusBadge = (status: TagRule["status"]) => {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return <Badge className="bg-green-100 text-green-800">运行中</Badge>
|
||||
case "inactive":
|
||||
return <Badge className="bg-yellow-100 text-yellow-800">已暂停</Badge>
|
||||
case "draft":
|
||||
return <Badge className="bg-gray-100 text-gray-800">草稿</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">标签规则引擎</h1>
|
||||
<p className="text-muted-foreground mt-1">管理和执行用户标签生成规则</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">
|
||||
<Clock className="mr-2 h-4 w-4" />
|
||||
执行历史
|
||||
</Button>
|
||||
<Button onClick={() => setIsCreateDialogOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
创建规则
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">规则总数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">24</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">较上月增加 3 个</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">活跃规则</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">18</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">占总规则的 75%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">今日执行次数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">36</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">影响用户 12,580 人</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="rules" className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<TabsList>
|
||||
<TabsTrigger value="rules">规则列表</TabsTrigger>
|
||||
<TabsTrigger value="editor">规则编辑器</TabsTrigger>
|
||||
<TabsTrigger value="history">执行历史</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex space-x-2">
|
||||
<div className="relative w-64">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索规则..."
|
||||
className="pl-8"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="rules" className="space-y-4">
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>规则名称</TableHead>
|
||||
<TableHead>目标标签</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>优先级</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>最后执行</TableHead>
|
||||
<TableHead>影响用户数</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredRules.map((rule) => (
|
||||
<TableRow key={rule.id}>
|
||||
<TableCell className="font-medium">
|
||||
<div>{rule.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{rule.description}</div>
|
||||
</TableCell>
|
||||
<TableCell>{rule.targetTag}</TableCell>
|
||||
<TableCell>{getStatusBadge(rule.status)}</TableCell>
|
||||
<TableCell>{rule.priority}</TableCell>
|
||||
<TableCell>{rule.createdAt}</TableCell>
|
||||
<TableCell>{rule.lastRun}</TableCell>
|
||||
<TableCell>{rule.affectedUsers.toLocaleString()}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setSelectedRuleId(rule.id)}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
{rule.status === "active" ? (
|
||||
<>
|
||||
<Pause className="mr-2 h-4 w-4" />
|
||||
暂停
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
启动
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<FileText className="mr-2 h-4 w-4" />
|
||||
查看详情
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="editor" className="space-y-4">
|
||||
<RuleEditor ruleId={selectedRuleId} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="history" className="space-y-4">
|
||||
<RuleExecutionHistory />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* 创建规则对话框 */}
|
||||
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建新规则</DialogTitle>
|
||||
<DialogDescription>添加新的标签生成规则</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="rule-name" className="text-right">
|
||||
规则名称
|
||||
</Label>
|
||||
<Input
|
||||
id="rule-name"
|
||||
value={newRule.name}
|
||||
onChange={(e) => setNewRule({ ...newRule, name: e.target.value })}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="rule-description" className="text-right">
|
||||
规则描述
|
||||
</Label>
|
||||
<Textarea
|
||||
id="rule-description"
|
||||
value={newRule.description}
|
||||
onChange={(e) => setNewRule({ ...newRule, description: e.target.value })}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="target-tag" className="text-right">
|
||||
目标标签
|
||||
</Label>
|
||||
<Input
|
||||
id="target-tag"
|
||||
value={newRule.targetTag}
|
||||
onChange={(e) => setNewRule({ ...newRule, targetTag: e.target.value })}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="rule-condition" className="text-right">
|
||||
规则条件
|
||||
</Label>
|
||||
<Textarea
|
||||
id="rule-condition"
|
||||
value={newRule.condition}
|
||||
onChange={(e) => setNewRule({ ...newRule, condition: e.target.value })}
|
||||
className="col-span-3"
|
||||
placeholder="例如: 消费金额 > 5000 AND 消费频次 > 10"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="rule-priority" className="text-right">
|
||||
优先级
|
||||
</Label>
|
||||
<Select
|
||||
value={newRule.priority.toString()}
|
||||
onValueChange={(value) => setNewRule({ ...newRule, priority: Number.parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger className="col-span-3">
|
||||
<SelectValue placeholder="选择优先级" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">1 - 高</SelectItem>
|
||||
<SelectItem value="2">2 - 中</SelectItem>
|
||||
<SelectItem value="3">3 - 低</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="rule-status" className="text-right">
|
||||
规则状态
|
||||
</Label>
|
||||
<div className="flex items-center space-x-2 col-span-3">
|
||||
<Switch
|
||||
id="rule-status"
|
||||
checked={newRule.status === "active"}
|
||||
onCheckedChange={(checked) => setNewRule({ ...newRule, status: checked ? "active" : "draft" })}
|
||||
/>
|
||||
<Label htmlFor="rule-status">{newRule.status === "active" ? "立即启用" : "保存为草稿"}</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleCreateRule}>创建</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function Loading() {
|
||||
return null
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Search, Plus, Filter, Calendar, CheckCircle2, XCircle, PlayCircle } from "lucide-react"
|
||||
import { TaskList } from "@/components/tag-tasks/task-list"
|
||||
|
||||
export default function TagTasksPage() {
|
||||
const [activeTab, setActiveTab] = useState("all")
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold">用户标签任务</h1>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline">
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
任务日历
|
||||
</Button>
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
创建任务
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">总任务数</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">128</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">本月新增 24 个任务</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">运行中任务</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<PlayCircle className="h-5 w-5 text-blue-500 mr-2" />
|
||||
<div className="text-2xl font-bold">12</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">预计完成时间 2小时后</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">已完成任务</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-500 mr-2" />
|
||||
<div className="text-2xl font-bold">98</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">成功率 96.2%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium">失败任务</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<XCircle className="h-5 w-5 text-red-500 mr-2" />
|
||||
<div className="text-2xl font-bold">18</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">失败率 3.8%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>标签任务管理</CardTitle>
|
||||
<CardDescription>创建和管理基于用户标签的自定义任务,自动化用户标签生成</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<TabsList>
|
||||
<TabsTrigger value="all">全部任务</TabsTrigger>
|
||||
<TabsTrigger value="running">运行中</TabsTrigger>
|
||||
<TabsTrigger value="completed">已完成</TabsTrigger>
|
||||
<TabsTrigger value="failed">失败</TabsTrigger>
|
||||
<TabsTrigger value="scheduled">已计划</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex space-x-2">
|
||||
<div className="relative w-64">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索任务..."
|
||||
className="pl-8"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="all" className="space-y-4">
|
||||
<TaskList status="all" searchQuery={searchQuery} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="running" className="space-y-4">
|
||||
<TaskList status="running" searchQuery={searchQuery} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="completed" className="space-y-4">
|
||||
<TaskList status="completed" searchQuery={searchQuery} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="failed" className="space-y-4">
|
||||
<TaskList status="failed" searchQuery={searchQuery} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="scheduled" className="space-y-4">
|
||||
<TaskList status="scheduled" searchQuery={searchQuery} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user