refactor: overhaul system navigation based on 5 HTML docs
Reorganize modules, add AI Agent smart interaction, include prompt settings, enhance data output with subscriptions, and separate system monitoring. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
464
app/data-output/api-market/page.tsx
Normal file
464
app/data-output/api-market/page.tsx
Normal file
@@ -0,0 +1,464 @@
|
||||
"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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import {
|
||||
Server, Plus, Key, FileText, BarChart3, Settings,
|
||||
Eye, Copy, Trash2, CheckCircle2, XCircle, Clock,
|
||||
Activity, ExternalLink, Code, Shield
|
||||
} from "lucide-react"
|
||||
|
||||
const apis = [
|
||||
{
|
||||
id: "1",
|
||||
name: "用户画像查询API",
|
||||
endpoint: "/api/v1/user/portrait",
|
||||
method: "GET",
|
||||
description: "根据用户ID查询完整画像信息",
|
||||
status: "active",
|
||||
callCount: 125680,
|
||||
avgLatency: 45,
|
||||
errorRate: 0.02,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "标签批量查询API",
|
||||
endpoint: "/api/v1/tags/batch",
|
||||
method: "POST",
|
||||
description: "批量查询用户标签",
|
||||
status: "active",
|
||||
callCount: 89420,
|
||||
avgLatency: 120,
|
||||
errorRate: 0.05,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "CLV评分API",
|
||||
endpoint: "/api/v1/clv/score",
|
||||
method: "GET",
|
||||
description: "获取用户CLV评分和预测值",
|
||||
status: "maintenance",
|
||||
callCount: 45230,
|
||||
avgLatency: 85,
|
||||
errorRate: 0.01,
|
||||
},
|
||||
]
|
||||
|
||||
const apiKeys = [
|
||||
{ id: "1", name: "生产环境密钥", key: "sk_prod_****8a9b", created: "2024-01-01", lastUsed: "2024-01-15", status: "active" },
|
||||
{ id: "2", name: "测试环境密钥", key: "sk_test_****3c2d", created: "2024-01-05", lastUsed: "2024-01-14", status: "active" },
|
||||
]
|
||||
|
||||
export default function APIMarketPage() {
|
||||
const [activeTab, setActiveTab] = useState("apis")
|
||||
const [showDocDialog, setShowDocDialog] = useState(false)
|
||||
const [showKeyDialog, setShowKeyDialog] = useState(false)
|
||||
const [showMonitorDialog, setShowMonitorDialog] = useState(false)
|
||||
const [selectedApi, setSelectedApi] = useState<typeof apis[0] | null>(null)
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return <Badge className="bg-green-100 text-green-700">运行中</Badge>
|
||||
case "maintenance":
|
||||
return <Badge className="bg-yellow-100 text-yellow-700">维护中</Badge>
|
||||
case "disabled":
|
||||
return <Badge className="bg-gray-100 text-gray-700">已停用</Badge>
|
||||
default:
|
||||
return <Badge variant="secondary">未知</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">API市场</h1>
|
||||
<p className="text-muted-foreground">管理和监控数据API服务</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowKeyDialog(true)}>
|
||||
<Key className="mr-2 h-4 w-4" />
|
||||
管理密钥
|
||||
</Button>
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
发布API
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">API总数</p>
|
||||
<p className="text-2xl font-bold">12</p>
|
||||
</div>
|
||||
<Server className="h-8 w-8 text-blue-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">今日调用</p>
|
||||
<p className="text-2xl font-bold">45.2K</p>
|
||||
</div>
|
||||
<Activity className="h-8 w-8 text-green-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">平均延迟</p>
|
||||
<p className="text-2xl font-bold">68ms</p>
|
||||
</div>
|
||||
<Clock className="h-8 w-8 text-purple-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">成功率</p>
|
||||
<p className="text-2xl font-bold">99.8%</p>
|
||||
</div>
|
||||
<CheckCircle2 className="h-8 w-8 text-cyan-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="apis">API列表</TabsTrigger>
|
||||
<TabsTrigger value="keys">API密钥</TabsTrigger>
|
||||
<TabsTrigger value="logs">调用日志</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="apis" className="space-y-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>API名称</TableHead>
|
||||
<TableHead>端点</TableHead>
|
||||
<TableHead>方法</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>调用量</TableHead>
|
||||
<TableHead>延迟</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{apis.map((api) => (
|
||||
<TableRow key={api.id}>
|
||||
<TableCell>
|
||||
<div>
|
||||
<p className="font-medium">{api.name}</p>
|
||||
<p className="text-sm text-muted-foreground">{api.description}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<code className="text-sm bg-muted px-2 py-1 rounded">{api.endpoint}</code>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{api.method}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(api.status)}</TableCell>
|
||||
<TableCell>{api.callCount.toLocaleString()}</TableCell>
|
||||
<TableCell>{api.avgLatency}ms</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedApi(api)
|
||||
setShowDocDialog(true)
|
||||
}}
|
||||
>
|
||||
<FileText className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedApi(api)
|
||||
setShowMonitorDialog(true)
|
||||
}}
|
||||
>
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="keys" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>API密钥</CardTitle>
|
||||
<CardDescription>管理API访问密钥</CardDescription>
|
||||
</div>
|
||||
<Button size="sm">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
创建密钥
|
||||
</Button>
|
||||
</div>
|
||||
</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="text-sm bg-muted px-2 py-1 rounded">{key.key}</code>
|
||||
</TableCell>
|
||||
<TableCell>{key.created}</TableCell>
|
||||
<TableCell>{key.lastUsed}</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-700">活跃</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm">
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="text-red-600">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="logs" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>调用日志</CardTitle>
|
||||
<CardDescription>查看API调用历史记录</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>时间</TableHead>
|
||||
<TableHead>API</TableHead>
|
||||
<TableHead>方法</TableHead>
|
||||
<TableHead>状态码</TableHead>
|
||||
<TableHead>延迟</TableHead>
|
||||
<TableHead>IP</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>2024-01-15 14:30:25</TableCell>
|
||||
<TableCell>/api/v1/user/portrait</TableCell>
|
||||
<TableCell><Badge variant="outline">GET</Badge></TableCell>
|
||||
<TableCell><Badge className="bg-green-100 text-green-700">200</Badge></TableCell>
|
||||
<TableCell>42ms</TableCell>
|
||||
<TableCell>192.168.1.***</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>2024-01-15 14:30:18</TableCell>
|
||||
<TableCell>/api/v1/tags/batch</TableCell>
|
||||
<TableCell><Badge variant="outline">POST</Badge></TableCell>
|
||||
<TableCell><Badge className="bg-green-100 text-green-700">200</Badge></TableCell>
|
||||
<TableCell>118ms</TableCell>
|
||||
<TableCell>192.168.1.***</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>2024-01-15 14:30:05</TableCell>
|
||||
<TableCell>/api/v1/clv/score</TableCell>
|
||||
<TableCell><Badge variant="outline">GET</Badge></TableCell>
|
||||
<TableCell><Badge className="bg-red-100 text-red-700">500</Badge></TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>192.168.2.***</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* API文档弹窗 */}
|
||||
<Dialog open={showDocDialog} onOpenChange={setShowDocDialog}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{selectedApi?.name} - 接口文档</DialogTitle>
|
||||
<DialogDescription>{selectedApi?.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">{selectedApi?.method}</Badge>
|
||||
<code className="flex-1 bg-muted px-3 py-2 rounded text-sm">{selectedApi?.endpoint}</code>
|
||||
<Button variant="outline" size="sm">
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium">请求参数</Label>
|
||||
<div className="mt-2 bg-muted rounded-lg p-4">
|
||||
<pre className="text-sm">{`{
|
||||
"user_id": "string (必填) - 用户唯一标识",
|
||||
"fields": "array (可选) - 需要返回的字段列表"
|
||||
}`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium">响应示例</Label>
|
||||
<div className="mt-2 bg-muted rounded-lg p-4">
|
||||
<pre className="text-sm">{`{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"user_id": "U12345",
|
||||
"portrait": {
|
||||
"basic": { "age": 28, "gender": "M" },
|
||||
"tags": ["高价值", "活跃用户"],
|
||||
"clv_score": 92
|
||||
}
|
||||
}
|
||||
}`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowDocDialog(false)}>关闭</Button>
|
||||
<Button>
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
在线调试
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 监控弹窗 */}
|
||||
<Dialog open={showMonitorDialog} onOpenChange={setShowMonitorDialog}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{selectedApi?.name} - 性能监控</DialogTitle>
|
||||
<DialogDescription>查看API实时性能指标</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-muted-foreground">今日调用</p>
|
||||
<p className="text-2xl font-bold">{selectedApi?.callCount.toLocaleString()}</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-muted-foreground">平均延迟</p>
|
||||
<p className="text-2xl font-bold">{selectedApi?.avgLatency}ms</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-muted-foreground">错误率</p>
|
||||
<p className="text-2xl font-bold">{((selectedApi?.errorRate || 0) * 100).toFixed(2)}%</p>
|
||||
</Card>
|
||||
</div>
|
||||
<Card className="p-4">
|
||||
<p className="text-sm font-medium mb-4">调用趋势 (最近24小时)</p>
|
||||
<div className="h-48 flex items-end justify-between gap-1">
|
||||
{Array.from({ length: 24 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex-1 bg-primary/20 rounded-t"
|
||||
style={{ height: `${Math.random() * 100}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowMonitorDialog(false)}>关闭</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 密钥管理弹窗 */}
|
||||
<Dialog open={showKeyDialog} onOpenChange={setShowKeyDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建API密钥</DialogTitle>
|
||||
<DialogDescription>创建新的API访问密钥</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>密钥名称</Label>
|
||||
<Input placeholder="输入密钥名称,如:生产环境密钥" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>权限范围</Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-muted">
|
||||
<input type="checkbox" defaultChecked />
|
||||
<span>用户画像API</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-muted">
|
||||
<input type="checkbox" defaultChecked />
|
||||
<span>标签查询API</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-muted">
|
||||
<input type="checkbox" />
|
||||
<span>CLV评分API</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 p-3 border rounded-lg cursor-pointer hover:bg-muted">
|
||||
<input type="checkbox" />
|
||||
<span>流量包API</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowKeyDialog(false)}>取消</Button>
|
||||
<Button>创建密钥</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
377
app/data-output/packages/page.tsx
Normal file
377
app/data-output/packages/page.tsx
Normal file
@@ -0,0 +1,377 @@
|
||||
"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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import {
|
||||
Package, Plus, Download, Eye, Trash2, RefreshCw,
|
||||
Users, Calendar, Tag, Filter, MoreHorizontal, Share2,
|
||||
CheckCircle2, Clock, AlertCircle
|
||||
} from "lucide-react"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
||||
const packages = [
|
||||
{
|
||||
id: "1",
|
||||
name: "高价值用户流量包",
|
||||
description: "CLV评分前20%的活跃用户",
|
||||
userCount: 15680,
|
||||
tags: ["高价值", "活跃用户"],
|
||||
status: "active",
|
||||
createdAt: "2024-01-10",
|
||||
lastUpdated: "2024-01-15",
|
||||
downloadCount: 23,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "潜力用户营销包",
|
||||
description: "近30天有浏览但未转化的用户",
|
||||
userCount: 8920,
|
||||
tags: ["潜力用户", "营销"],
|
||||
status: "active",
|
||||
createdAt: "2024-01-08",
|
||||
lastUpdated: "2024-01-14",
|
||||
downloadCount: 15,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "流失预警用户包",
|
||||
description: "预测30天内可能流失的用户",
|
||||
userCount: 3240,
|
||||
tags: ["流失预警", "挽回"],
|
||||
status: "updating",
|
||||
createdAt: "2024-01-05",
|
||||
lastUpdated: "2024-01-15",
|
||||
downloadCount: 8,
|
||||
},
|
||||
]
|
||||
|
||||
export default function PackagesPage() {
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
const [showPreviewDialog, setShowPreviewDialog] = useState(false)
|
||||
const [selectedPackage, setSelectedPackage] = useState<typeof packages[0] | null>(null)
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return <Badge className="bg-green-100 text-green-700">可用</Badge>
|
||||
case "updating":
|
||||
return <Badge className="bg-blue-100 text-blue-700">更新中</Badge>
|
||||
case "expired":
|
||||
return <Badge className="bg-gray-100 text-gray-700">已过期</Badge>
|
||||
default:
|
||||
return <Badge variant="secondary">未知</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">流量包管理</h1>
|
||||
<p className="text-muted-foreground">创建和管理用户流量包,支持多维度筛选和导出</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
创建流量包
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">流量包总数</p>
|
||||
<p className="text-2xl font-bold">24</p>
|
||||
</div>
|
||||
<Package className="h-8 w-8 text-blue-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">覆盖用户</p>
|
||||
<p className="text-2xl font-bold">156.8K</p>
|
||||
</div>
|
||||
<Users className="h-8 w-8 text-green-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">本月下载</p>
|
||||
<p className="text-2xl font-bold">89</p>
|
||||
</div>
|
||||
<Download className="h-8 w-8 text-purple-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">活跃包</p>
|
||||
<p className="text-2xl font-bold">18</p>
|
||||
</div>
|
||||
<CheckCircle2 className="h-8 w-8 text-cyan-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 流量包列表 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>流量包列表</CardTitle>
|
||||
<CardDescription>管理所有已创建的用户流量包</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input placeholder="搜索流量包..." className="w-64" />
|
||||
<Button variant="outline" size="icon">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>名称</TableHead>
|
||||
<TableHead>用户数</TableHead>
|
||||
<TableHead>标签</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>更新时间</TableHead>
|
||||
<TableHead>下载次数</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{packages.map((pkg) => (
|
||||
<TableRow key={pkg.id}>
|
||||
<TableCell>
|
||||
<div>
|
||||
<p className="font-medium">{pkg.name}</p>
|
||||
<p className="text-sm text-muted-foreground">{pkg.description}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{pkg.userCount.toLocaleString()}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
{pkg.tags.map((tag) => (
|
||||
<Badge key={tag} variant="outline" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(pkg.status)}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{pkg.lastUpdated}</TableCell>
|
||||
<TableCell>{pkg.downloadCount}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedPackage(pkg)
|
||||
setShowPreviewDialog(true)
|
||||
}}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
刷新数据
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Share2 className="h-4 w-4 mr-2" />
|
||||
分享
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 创建流量包弹窗 */}
|
||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建流量包</DialogTitle>
|
||||
<DialogDescription>通过设置筛选条件创建用户流量包</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>流量包名称</Label>
|
||||
<Input placeholder="输入流量包名称" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>描述</Label>
|
||||
<Input placeholder="输入流量包描述" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>筛选条件</Label>
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择字段" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="clv_score">CLV评分</SelectItem>
|
||||
<SelectItem value="rfm_level">RFM等级</SelectItem>
|
||||
<SelectItem value="last_active">最后活跃</SelectItem>
|
||||
<SelectItem value="purchase_count">购买次数</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择条件" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="gt">大于</SelectItem>
|
||||
<SelectItem value="lt">小于</SelectItem>
|
||||
<SelectItem value="eq">等于</SelectItem>
|
||||
<SelectItem value="between">介于</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input placeholder="输入值" />
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
添加条件
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>标签</Label>
|
||||
<Input placeholder="输入标签,用逗号分隔" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowCreateDialog(false)}>取消</Button>
|
||||
<Button>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
预览结果
|
||||
</Button>
|
||||
<Button>创建</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 预览弹窗 */}
|
||||
<Dialog open={showPreviewDialog} onOpenChange={setShowPreviewDialog}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>流量包预览 - {selectedPackage?.name}</DialogTitle>
|
||||
<DialogDescription>{selectedPackage?.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-muted-foreground">用户总数</p>
|
||||
<p className="text-2xl font-bold">{selectedPackage?.userCount.toLocaleString()}</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-muted-foreground">平均CLV</p>
|
||||
<p className="text-2xl font-bold">¥2,580</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-muted-foreground">活跃率</p>
|
||||
<p className="text-2xl font-bold">78.5%</p>
|
||||
</Card>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">用户样本</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>用户ID</TableHead>
|
||||
<TableHead>CLV评分</TableHead>
|
||||
<TableHead>RFM等级</TableHead>
|
||||
<TableHead>最后活跃</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>U****001</TableCell>
|
||||
<TableCell>92</TableCell>
|
||||
<TableCell>A</TableCell>
|
||||
<TableCell>2024-01-15</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>U****002</TableCell>
|
||||
<TableCell>88</TableCell>
|
||||
<TableCell>A</TableCell>
|
||||
<TableCell>2024-01-14</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>U****003</TableCell>
|
||||
<TableCell>85</TableCell>
|
||||
<TableCell>B</TableCell>
|
||||
<TableCell>2024-01-15</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowPreviewDialog(false)}>关闭</Button>
|
||||
<Button>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
导出数据
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
243
app/data-output/subscription/page.tsx
Normal file
243
app/data-output/subscription/page.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"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 { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import {
|
||||
Webhook, Plus, Settings, Trash2, Play, Pause,
|
||||
CheckCircle2, XCircle, Clock, RefreshCw, Eye
|
||||
} from "lucide-react"
|
||||
|
||||
const subscriptions = [
|
||||
{
|
||||
id: "1",
|
||||
name: "用户标签变更通知",
|
||||
type: "webhook",
|
||||
endpoint: "https://api.example.com/webhook/tags",
|
||||
event: "tag_changed",
|
||||
status: "active",
|
||||
lastTriggered: "2024-01-15 14:20:00",
|
||||
successRate: 99.5,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "高价值用户预警",
|
||||
type: "webhook",
|
||||
endpoint: "https://api.example.com/webhook/alerts",
|
||||
event: "high_value_alert",
|
||||
status: "active",
|
||||
lastTriggered: "2024-01-15 10:15:00",
|
||||
successRate: 98.2,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "数据同步完成通知",
|
||||
type: "email",
|
||||
endpoint: "admin@example.com",
|
||||
event: "sync_completed",
|
||||
status: "paused",
|
||||
lastTriggered: "2024-01-14 23:00:00",
|
||||
successRate: 100,
|
||||
},
|
||||
]
|
||||
|
||||
export default function SubscriptionPage() {
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return <Badge className="bg-green-100 text-green-700">运行中</Badge>
|
||||
case "paused":
|
||||
return <Badge className="bg-yellow-100 text-yellow-700">已暂停</Badge>
|
||||
case "error":
|
||||
return <Badge className="bg-red-100 text-red-700">异常</Badge>
|
||||
default:
|
||||
return <Badge variant="secondary">未知</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">数据订阅</h1>
|
||||
<p className="text-muted-foreground">配置数据变更的实时推送和通知</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowCreateDialog(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
创建订阅
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">订阅总数</p>
|
||||
<p className="text-2xl font-bold">8</p>
|
||||
</div>
|
||||
<Webhook className="h-8 w-8 text-blue-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">今日推送</p>
|
||||
<p className="text-2xl font-bold">1,256</p>
|
||||
</div>
|
||||
<RefreshCw className="h-8 w-8 text-green-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">成功率</p>
|
||||
<p className="text-2xl font-bold">99.2%</p>
|
||||
</div>
|
||||
<CheckCircle2 className="h-8 w-8 text-purple-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">活跃订阅</p>
|
||||
<p className="text-2xl font-bold">6</p>
|
||||
</div>
|
||||
<Play className="h-8 w-8 text-cyan-500" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</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 className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{subscriptions.map((sub) => (
|
||||
<TableRow key={sub.id}>
|
||||
<TableCell className="font-medium">{sub.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{sub.type === "webhook" ? "Webhook" : "邮件"}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<code className="text-xs bg-muted px-2 py-1 rounded truncate max-w-[200px] block">
|
||||
{sub.endpoint}
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell>{sub.event}</TableCell>
|
||||
<TableCell>{getStatusBadge(sub.status)}</TableCell>
|
||||
<TableCell>{sub.successRate}%</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm">
|
||||
{sub.status === "active" ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="text-red-600">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 创建订阅弹窗 */}
|
||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建数据订阅</DialogTitle>
|
||||
<DialogDescription>配置数据变更的实时推送</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>订阅名称</Label>
|
||||
<Input placeholder="输入订阅名称" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>推送类型</Label>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择推送类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="webhook">Webhook</SelectItem>
|
||||
<SelectItem value="email">邮件通知</SelectItem>
|
||||
<SelectItem value="sms">短信通知</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>目标地址</Label>
|
||||
<Input placeholder="输入Webhook URL或邮箱地址" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>触发事件</Label>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择触发事件" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="tag_changed">标签变更</SelectItem>
|
||||
<SelectItem value="portrait_updated">画像更新</SelectItem>
|
||||
<SelectItem value="high_value_alert">高价值用户预警</SelectItem>
|
||||
<SelectItem value="churn_alert">流失预警</SelectItem>
|
||||
<SelectItem value="sync_completed">数据同步完成</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-3 bg-muted rounded-lg">
|
||||
<div>
|
||||
<Label>启用订阅</Label>
|
||||
<p className="text-sm text-muted-foreground">创建后立即开始推送</p>
|
||||
</div>
|
||||
<Switch defaultChecked />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowCreateDialog(false)}>取消</Button>
|
||||
<Button>创建订阅</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user