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:
@@ -1,41 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts"
|
||||
|
||||
const data = [
|
||||
{ name: "用户价值", value: 25 },
|
||||
{ name: "人口属性", value: 18 },
|
||||
{ name: "兴趣爱好", value: 22 },
|
||||
{ name: "流失风险", value: 12 },
|
||||
{ name: "地理位置", value: 15 },
|
||||
{ name: "活跃时间", value: 10 },
|
||||
{ name: "消费能力", value: 14 },
|
||||
{ name: "渠道来源", value: 12 },
|
||||
]
|
||||
|
||||
const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042", "#8884d8", "#82ca9d", "#ffc658", "#8dd1e1"]
|
||||
|
||||
export function TagCategoryChart() {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={data}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef } from "react"
|
||||
import * as d3 from "d3"
|
||||
|
||||
interface Node {
|
||||
id: string
|
||||
name: string
|
||||
category: string
|
||||
value: number
|
||||
}
|
||||
|
||||
interface Link {
|
||||
source: string
|
||||
target: string
|
||||
value: number
|
||||
}
|
||||
|
||||
const mockData = {
|
||||
nodes: [
|
||||
{ id: "1", name: "高价值用户", category: "用户价值", value: 20 },
|
||||
{ id: "2", name: "90后", category: "人口属性", value: 15 },
|
||||
{ id: "3", name: "游戏爱好者", category: "兴趣爱好", value: 18 },
|
||||
{ id: "4", name: "流失风险高", category: "流失风险", value: 12 },
|
||||
{ id: "5", name: "北京地区", category: "地理位置", value: 10 },
|
||||
{ id: "6", name: "周末活跃", category: "活跃时间", value: 14 },
|
||||
{ id: "7", name: "高消费", category: "消费能力", value: 16 },
|
||||
{ id: "8", name: "App渠道", category: "渠道来源", value: 8 },
|
||||
],
|
||||
links: [
|
||||
{ source: "1", target: "7", value: 5 },
|
||||
{ source: "2", target: "3", value: 8 },
|
||||
{ source: "2", target: "6", value: 3 },
|
||||
{ source: "3", target: "7", value: 6 },
|
||||
{ source: "4", target: "6", value: 4 },
|
||||
{ source: "5", target: "8", value: 2 },
|
||||
{ source: "6", target: "3", value: 7 },
|
||||
{ source: "7", target: "8", value: 3 },
|
||||
{ source: "1", target: "3", value: 5 },
|
||||
{ source: "2", target: "5", value: 4 },
|
||||
{ source: "4", target: "1", value: 6 },
|
||||
],
|
||||
}
|
||||
|
||||
const categoryColors = {
|
||||
用户价值: "#0088FE",
|
||||
人口属性: "#00C49F",
|
||||
兴趣爱好: "#FFBB28",
|
||||
流失风险: "#FF8042",
|
||||
地理位置: "#8884d8",
|
||||
活跃时间: "#82ca9d",
|
||||
消费能力: "#ffc658",
|
||||
渠道来源: "#8dd1e1",
|
||||
}
|
||||
|
||||
export function TagRelationshipGraph() {
|
||||
const svgRef = useRef<SVGSVGElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!svgRef.current) return
|
||||
|
||||
const svg = d3.select(svgRef.current)
|
||||
svg.selectAll("*").remove()
|
||||
|
||||
const width = svgRef.current.clientWidth
|
||||
const height = svgRef.current.clientHeight
|
||||
|
||||
// 创建力导向图
|
||||
const simulation = d3
|
||||
.forceSimulation(mockData.nodes as d3.SimulationNodeDatum[])
|
||||
.force(
|
||||
"link",
|
||||
d3
|
||||
.forceLink(mockData.links)
|
||||
.id((d: any) => d.id)
|
||||
.distance(100),
|
||||
)
|
||||
.force("charge", d3.forceManyBody().strength(-200))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
|
||||
// 绘制连线
|
||||
const link = svg
|
||||
.append("g")
|
||||
.attr("stroke", "#999")
|
||||
.attr("stroke-opacity", 0.6)
|
||||
.selectAll("line")
|
||||
.data(mockData.links)
|
||||
.join("line")
|
||||
.attr("stroke-width", (d) => Math.sqrt(d.value))
|
||||
|
||||
// 绘制节点
|
||||
const node = svg
|
||||
.append("g")
|
||||
.selectAll("circle")
|
||||
.data(mockData.nodes)
|
||||
.join("circle")
|
||||
.attr("r", (d) => d.value * 0.8)
|
||||
.attr("fill", (d) => categoryColors[d.category as keyof typeof categoryColors])
|
||||
.call(d3.drag<SVGCircleElement, Node>().on("start", dragstarted).on("drag", dragged).on("end", dragended) as any)
|
||||
|
||||
// 添加标签
|
||||
const text = svg
|
||||
.append("g")
|
||||
.selectAll("text")
|
||||
.data(mockData.nodes)
|
||||
.join("text")
|
||||
.text((d) => d.name)
|
||||
.attr("font-size", 10)
|
||||
.attr("dx", 15)
|
||||
.attr("dy", 4)
|
||||
|
||||
// 更新位置
|
||||
simulation.on("tick", () => {
|
||||
link
|
||||
.attr("x1", (d: any) => d.source.x)
|
||||
.attr("y1", (d: any) => d.source.y)
|
||||
.attr("x2", (d: any) => d.target.x)
|
||||
.attr("y2", (d: any) => d.target.y)
|
||||
|
||||
node.attr("cx", (d: any) => d.x).attr("cy", (d: any) => d.y)
|
||||
|
||||
text.attr("x", (d: any) => d.x).attr("y", (d: any) => d.y)
|
||||
})
|
||||
|
||||
// 拖拽函数
|
||||
function dragstarted(event: any, d: any) {
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart()
|
||||
d.fx = d.x
|
||||
d.fy = d.y
|
||||
}
|
||||
|
||||
function dragged(event: any, d: any) {
|
||||
d.fx = event.x
|
||||
d.fy = event.y
|
||||
}
|
||||
|
||||
function dragended(event: any, d: any) {
|
||||
if (!event.active) simulation.alphaTarget(0)
|
||||
d.fx = null
|
||||
d.fy = null
|
||||
}
|
||||
|
||||
return () => {
|
||||
simulation.stop()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <svg ref={svgRef} width="100%" height="100%" />
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: "营销系统",
|
||||
使用标签数: 85,
|
||||
},
|
||||
{
|
||||
name: "推荐系统",
|
||||
使用标签数: 72,
|
||||
},
|
||||
{
|
||||
name: "客服系统",
|
||||
使用标签数: 45,
|
||||
},
|
||||
{
|
||||
name: "风控系统",
|
||||
使用标签数: 38,
|
||||
},
|
||||
{
|
||||
name: "内容系统",
|
||||
使用标签数: 65,
|
||||
},
|
||||
]
|
||||
|
||||
export function TagUsageStats() {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart
|
||||
data={data}
|
||||
margin={{
|
||||
top: 5,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="使用标签数" fill="#8884d8" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Trash2, Plus, Save } from "lucide-react"
|
||||
|
||||
interface RuleCondition {
|
||||
field: string
|
||||
operator: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface TagRule {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
conditions: RuleCondition[]
|
||||
actions: {
|
||||
addTags: string[]
|
||||
removeTags: string[]
|
||||
}
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
interface RuleEditorProps {
|
||||
rule?: TagRule
|
||||
onSave: (rule: TagRule) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export function RuleEditor({ rule, onSave, onCancel }: RuleEditorProps) {
|
||||
const [formData, setFormData] = useState<TagRule>(
|
||||
rule || {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
conditions: [{ field: "", operator: "", value: "" }],
|
||||
actions: {
|
||||
addTags: [],
|
||||
removeTags: [],
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
)
|
||||
|
||||
const [newAddTag, setNewAddTag] = useState("")
|
||||
const [newRemoveTag, setNewRemoveTag] = useState("")
|
||||
|
||||
const fieldOptions = [
|
||||
{ value: "imei", label: "IMEI" },
|
||||
{ value: "phone", label: "手机号" },
|
||||
{ value: "device_brand", label: "设备品牌" },
|
||||
{ value: "device_model", label: "设备型号" },
|
||||
{ value: "os_version", label: "系统版本" },
|
||||
{ value: "app_version", label: "应用版本" },
|
||||
{ value: "location", label: "地理位置" },
|
||||
{ value: "user_behavior", label: "用户行为" },
|
||||
{ value: "consumption_amount", label: "消费金额" },
|
||||
{ value: "activity_frequency", label: "活跃频率" },
|
||||
]
|
||||
|
||||
const operatorOptions = [
|
||||
{ value: "equals", label: "等于" },
|
||||
{ value: "not_equals", label: "不等于" },
|
||||
{ value: "contains", label: "包含" },
|
||||
{ value: "not_contains", label: "不包含" },
|
||||
{ value: "starts_with", label: "开始于" },
|
||||
{ value: "ends_with", label: "结束于" },
|
||||
{ value: "greater_than", label: "大于" },
|
||||
{ value: "less_than", label: "小于" },
|
||||
{ value: "in_range", label: "在范围内" },
|
||||
{ value: "regex", label: "正则匹配" },
|
||||
]
|
||||
|
||||
const addCondition = () => {
|
||||
setFormData({
|
||||
...formData,
|
||||
conditions: [...formData.conditions, { field: "", operator: "", value: "" }],
|
||||
})
|
||||
}
|
||||
|
||||
const removeCondition = (index: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
conditions: formData.conditions.filter((_, i) => i !== index),
|
||||
})
|
||||
}
|
||||
|
||||
const updateCondition = (index: number, field: keyof RuleCondition, value: string) => {
|
||||
const newConditions = [...formData.conditions]
|
||||
newConditions[index] = { ...newConditions[index], [field]: value }
|
||||
setFormData({ ...formData, conditions: newConditions })
|
||||
}
|
||||
|
||||
const addTag = (type: "addTags" | "removeTags") => {
|
||||
const tagValue = type === "addTags" ? newAddTag : newRemoveTag
|
||||
if (tagValue.trim()) {
|
||||
setFormData({
|
||||
...formData,
|
||||
actions: {
|
||||
...formData.actions,
|
||||
[type]: [...formData.actions[type], tagValue.trim()],
|
||||
},
|
||||
})
|
||||
if (type === "addTags") {
|
||||
setNewAddTag("")
|
||||
} else {
|
||||
setNewRemoveTag("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeTag = (type: "addTags" | "removeTags", index: number) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
actions: {
|
||||
...formData.actions,
|
||||
[type]: formData.actions[type].filter((_, i) => i !== index),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
if (formData.name && formData.conditions.length > 0) {
|
||||
onSave(formData)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>规则基本信息</CardTitle>
|
||||
<CardDescription>设置标签规则的基本信息</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="rule-name">规则名称</Label>
|
||||
<Input
|
||||
id="rule-name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="输入规则名称"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="rule-description">规则描述</Label>
|
||||
<Input
|
||||
id="rule-description"
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder="输入规则描述"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>触发条件</CardTitle>
|
||||
<CardDescription>设置标签规则的触发条件</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{formData.conditions.map((condition, index) => (
|
||||
<div key={index} className="flex items-center space-x-2 p-3 border rounded-lg">
|
||||
<Select value={condition.field} onValueChange={(value) => updateCondition(index, "field", value)}>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectValue placeholder="选择字段" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fieldOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={condition.operator} onValueChange={(value) => updateCondition(index, "operator", value)}>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue placeholder="选择操作符" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{operatorOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Input
|
||||
value={condition.value}
|
||||
onChange={(e) => updateCondition(index, "value", e.target.value)}
|
||||
placeholder="输入值"
|
||||
className="flex-1"
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => removeCondition(index)}
|
||||
disabled={formData.conditions.length === 1}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button variant="outline" onClick={addCondition}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
添加条件
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>执行动作</CardTitle>
|
||||
<CardDescription>设置满足条件时的标签操作</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<Label>添加标签</Label>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Input
|
||||
value={newAddTag}
|
||||
onChange={(e) => setNewAddTag(e.target.value)}
|
||||
placeholder="输入要添加的标签"
|
||||
onKeyPress={(e) => e.key === "Enter" && addTag("addTags")}
|
||||
/>
|
||||
<Button onClick={() => addTag("addTags")}>添加</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.actions.addTags.map((tag, index) => (
|
||||
<Badge key={index} variant="secondary" className="flex items-center gap-1">
|
||||
{tag}
|
||||
<button onClick={() => removeTag("addTags", index)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>移除标签</Label>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<Input
|
||||
value={newRemoveTag}
|
||||
onChange={(e) => setNewRemoveTag(e.target.value)}
|
||||
placeholder="输入要移除的标签"
|
||||
onKeyPress={(e) => e.key === "Enter" && addTag("removeTags")}
|
||||
/>
|
||||
<Button onClick={() => addTag("removeTags")}>添加</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.actions.removeTags.map((tag, index) => (
|
||||
<Badge key={index} variant="destructive" className="flex items-center gap-1">
|
||||
{tag}
|
||||
<button onClick={() => removeTag("removeTags", index)}>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
保存规则
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Search, Filter, Calendar, FileText } from "lucide-react"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
|
||||
interface ExecutionRecord {
|
||||
id: string
|
||||
ruleName: string
|
||||
executionTime: string
|
||||
duration: string
|
||||
status: "success" | "failed" | "running"
|
||||
affectedUsers: number
|
||||
executedBy: string
|
||||
}
|
||||
|
||||
const mockExecutionHistory: ExecutionRecord[] = [
|
||||
{
|
||||
id: "exec-001",
|
||||
ruleName: "高价值用户识别",
|
||||
executionTime: "2023-07-21 15:30:45",
|
||||
duration: "45秒",
|
||||
status: "success",
|
||||
affectedUsers: 1250,
|
||||
executedBy: "系统自动",
|
||||
},
|
||||
{
|
||||
id: "exec-002",
|
||||
ruleName: "游戏爱好者标记",
|
||||
executionTime: "2023-07-21 14:45:12",
|
||||
duration: "38秒",
|
||||
status: "success",
|
||||
affectedUsers: 2840,
|
||||
executedBy: "系统自动",
|
||||
},
|
||||
{
|
||||
id: "exec-003",
|
||||
ruleName: "流失风险预警",
|
||||
executionTime: "2023-07-21 13:20:33",
|
||||
duration: "52秒",
|
||||
status: "success",
|
||||
affectedUsers: 890,
|
||||
executedBy: "系统自动",
|
||||
},
|
||||
{
|
||||
id: "exec-004",
|
||||
ruleName: "高价值用户识别",
|
||||
executionTime: "2023-07-20 15:30:18",
|
||||
duration: "47秒",
|
||||
status: "success",
|
||||
affectedUsers: 1235,
|
||||
executedBy: "系统自动",
|
||||
},
|
||||
{
|
||||
id: "exec-005",
|
||||
ruleName: "周末活跃用户",
|
||||
executionTime: "2023-07-20 12:15:42",
|
||||
duration: "1分15秒",
|
||||
status: "success",
|
||||
affectedUsers: 3520,
|
||||
executedBy: "admin",
|
||||
},
|
||||
{
|
||||
id: "exec-006",
|
||||
ruleName: "潜在高转化用户",
|
||||
executionTime: "2023-07-19 16:45:30",
|
||||
duration: "2分08秒",
|
||||
status: "failed",
|
||||
affectedUsers: 0,
|
||||
executedBy: "admin",
|
||||
},
|
||||
{
|
||||
id: "exec-007",
|
||||
ruleName: "高价值用户识别",
|
||||
executionTime: "2023-07-19 15:30:22",
|
||||
duration: "46秒",
|
||||
status: "success",
|
||||
affectedUsers: 1228,
|
||||
executedBy: "系统自动",
|
||||
},
|
||||
]
|
||||
|
||||
export function RuleExecutionHistory() {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedRecord, setSelectedRecord] = useState<ExecutionRecord | null>(null)
|
||||
const [isDetailsOpen, setIsDetailsOpen] = useState(false)
|
||||
|
||||
const filteredHistory = mockExecutionHistory.filter(
|
||||
(record) =>
|
||||
record.ruleName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.executedBy.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
)
|
||||
|
||||
const getStatusBadge = (status: ExecutionRecord["status"]) => {
|
||||
switch (status) {
|
||||
case "success":
|
||||
return <Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
case "failed":
|
||||
return <Badge className="bg-red-100 text-red-800">失败</Badge>
|
||||
case "running":
|
||||
return <Badge className="bg-blue-100 text-blue-800">执行中</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
const handleViewDetails = (record: ExecutionRecord) => {
|
||||
setSelectedRecord(record)
|
||||
setIsDetailsOpen(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-medium">规则执行历史</h3>
|
||||
<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>
|
||||
<Button variant="outline" size="icon">
|
||||
<Calendar className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>执行ID</TableHead>
|
||||
<TableHead>规则名称</TableHead>
|
||||
<TableHead>执行时间</TableHead>
|
||||
<TableHead>耗时</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>影响用户数</TableHead>
|
||||
<TableHead>执行人</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredHistory.map((record) => (
|
||||
<TableRow key={record.id}>
|
||||
<TableCell className="font-mono text-xs">{record.id}</TableCell>
|
||||
<TableCell>{record.ruleName}</TableCell>
|
||||
<TableCell>{record.executionTime}</TableCell>
|
||||
<TableCell>{record.duration}</TableCell>
|
||||
<TableCell>{getStatusBadge(record.status)}</TableCell>
|
||||
<TableCell>{record.affectedUsers.toLocaleString()}</TableCell>
|
||||
<TableCell>{record.executedBy}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleViewDetails(record)}>
|
||||
<FileText className="h-4 w-4 mr-2" />
|
||||
详情
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 执行详情对话框 */}
|
||||
<Dialog open={isDetailsOpen} onOpenChange={setIsDetailsOpen}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>执行详情</DialogTitle>
|
||||
<DialogDescription>规则执行的详细信息</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedRecord && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">执行ID</p>
|
||||
<p className="font-mono">{selectedRecord.id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">规则名称</p>
|
||||
<p>{selectedRecord.ruleName}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">执行时间</p>
|
||||
<p>{selectedRecord.executionTime}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">耗时</p>
|
||||
<p>{selectedRecord.duration}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">状态</p>
|
||||
<p>{getStatusBadge(selectedRecord.status)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">影响用户数</p>
|
||||
<p>{selectedRecord.affectedUsers.toLocaleString()}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">执行人</p>
|
||||
<p>{selectedRecord.executedBy}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 mb-2">执行日志</p>
|
||||
<div className="bg-gray-50 p-4 rounded-md font-mono text-xs h-40 overflow-y-auto">
|
||||
{selectedRecord.status === "success" ? (
|
||||
<>
|
||||
<p>
|
||||
[INFO] {selectedRecord.executionTime} - 开始执行规则 "{selectedRecord.ruleName}"
|
||||
</p>
|
||||
<p>[INFO] {selectedRecord.executionTime} - 规则条件解析完成</p>
|
||||
<p>[INFO] {selectedRecord.executionTime} - 开始查询符合条件的用户</p>
|
||||
<p>
|
||||
[INFO] {selectedRecord.executionTime} - 查询完成,找到 {selectedRecord.affectedUsers}{" "}
|
||||
个符合条件的用户
|
||||
</p>
|
||||
<p>[INFO] {selectedRecord.executionTime} - 开始为用户打标签</p>
|
||||
<p>[INFO] {selectedRecord.executionTime} - 标签应用完成</p>
|
||||
<p>
|
||||
[INFO] {selectedRecord.executionTime} - 规则执行成功,耗时 {selectedRecord.duration}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
[INFO] {selectedRecord.executionTime} - 开始执行规则 "{selectedRecord.ruleName}"
|
||||
</p>
|
||||
<p>[INFO] {selectedRecord.executionTime} - 规则条件解析完成</p>
|
||||
<p>[INFO] {selectedRecord.executionTime} - 开始查询符合条件的用户</p>
|
||||
<p>[ERROR] {selectedRecord.executionTime} - 查询执行失败: 数据库连接超时</p>
|
||||
<p>
|
||||
[ERROR] {selectedRecord.executionTime} - 规则执行失败,耗时 {selectedRecord.duration}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,307 +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 { Label } from "@/components/ui/label"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Calendar } from "@/components/ui/calendar"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { CalendarIcon, Plus, Tag, X } from "lucide-react"
|
||||
import { format } from "date-fns"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function TaskCreation() {
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([])
|
||||
const [newTag, setNewTag] = useState("")
|
||||
const [date, setDate] = useState<Date>()
|
||||
|
||||
const addTag = () => {
|
||||
if (newTag && !selectedTags.includes(newTag)) {
|
||||
setSelectedTags([...selectedTags, newTag])
|
||||
setNewTag("")
|
||||
}
|
||||
}
|
||||
|
||||
const removeTag = (tag: string) => {
|
||||
setSelectedTags(selectedTags.filter((t) => t !== tag))
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>创建标签任务</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="basic" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="basic">基本信息</TabsTrigger>
|
||||
<TabsTrigger value="target">目标设置</TabsTrigger>
|
||||
<TabsTrigger value="action">动作配置</TabsTrigger>
|
||||
<TabsTrigger value="schedule">计划设置</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="basic" className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task-name">任务名称</Label>
|
||||
<Input id="task-name" placeholder="输入任务名称" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task-description">任务描述</Label>
|
||||
<Textarea id="task-description" placeholder="输入任务描述" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="platform">目标平台</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="platform">
|
||||
<SelectValue placeholder="选择目标平台" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="jd">京东</SelectItem>
|
||||
<SelectItem value="taobao">淘宝</SelectItem>
|
||||
<SelectItem value="dangdang">当当网</SelectItem>
|
||||
<SelectItem value="xiaohongshu">小红书</SelectItem>
|
||||
<SelectItem value="zhihu">知乎</SelectItem>
|
||||
<SelectItem value="bilibili">哔哩哔哩</SelectItem>
|
||||
<SelectItem value="douyin">抖音</SelectItem>
|
||||
<SelectItem value="other">其他平台</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="priority">任务优先级</Label>
|
||||
<Select defaultValue="medium">
|
||||
<SelectTrigger id="priority">
|
||||
<SelectValue placeholder="选择优先级" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="high">高</SelectItem>
|
||||
<SelectItem value="medium">中</SelectItem>
|
||||
<SelectItem value="low">低</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="target" className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>目标用户标签</Label>
|
||||
<div className="flex flex-wrap gap-2 mb-2">
|
||||
{selectedTags.map((tag) => (
|
||||
<Badge key={tag} variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
{tag}
|
||||
<button onClick={() => removeTag(tag)} className="ml-1">
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
placeholder="输入标签名称"
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && addTag()}
|
||||
/>
|
||||
<Button type="button" size="sm" onClick={addTag}>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user-segment">用户分群</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="user-segment">
|
||||
<SelectValue placeholder="选择用户分群" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部用户</SelectItem>
|
||||
<SelectItem value="active">活跃用户</SelectItem>
|
||||
<SelectItem value="new">新注册用户</SelectItem>
|
||||
<SelectItem value="high-value">高价值用户</SelectItem>
|
||||
<SelectItem value="inactive">不活跃用户</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="user-count">目标用户数量</Label>
|
||||
<Input id="user-count" type="number" placeholder="输入目标用户数量" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sampling-method">抽样方式</Label>
|
||||
<Select defaultValue="random">
|
||||
<SelectTrigger id="sampling-method">
|
||||
<SelectValue placeholder="选择抽样方式" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="random">随机抽样</SelectItem>
|
||||
<SelectItem value="stratified">分层抽样</SelectItem>
|
||||
<SelectItem value="systematic">系统抽样</SelectItem>
|
||||
<SelectItem value="all">全量用户</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="action" className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="action-type">动作类型</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="action-type">
|
||||
<SelectValue placeholder="选择动作类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="login">登录检测</SelectItem>
|
||||
<SelectItem value="purchase">购买记录检测</SelectItem>
|
||||
<SelectItem value="browse">浏览行为检测</SelectItem>
|
||||
<SelectItem value="search">搜索行为检测</SelectItem>
|
||||
<SelectItem value="content">内容偏好检测</SelectItem>
|
||||
<SelectItem value="custom">自定义动作</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="action-params">动作参数</Label>
|
||||
<Textarea id="action-params" placeholder="输入动作参数(JSON格式)" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="success-criteria">成功标准</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="success-criteria">
|
||||
<SelectValue placeholder="选择成功标准" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="login-success">登录成功</SelectItem>
|
||||
<SelectItem value="purchase-complete">完成购买</SelectItem>
|
||||
<SelectItem value="browse-time">浏览时间超过阈值</SelectItem>
|
||||
<SelectItem value="search-count">搜索次数超过阈值</SelectItem>
|
||||
<SelectItem value="content-interaction">内容互动</SelectItem>
|
||||
<SelectItem value="custom">自定义标准</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>新增标签设置</Label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input placeholder="输入新标签名称" />
|
||||
<Select>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="标签分类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="behavior">行为特征</SelectItem>
|
||||
<SelectItem value="preference">偏好特征</SelectItem>
|
||||
<SelectItem value="value">价值特征</SelectItem>
|
||||
<SelectItem value="lifecycle">生命周期</SelectItem>
|
||||
<SelectItem value="custom">自定义分类</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button type="button" size="sm">
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="auto-apply" />
|
||||
<Label htmlFor="auto-apply">自动应用标签</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="schedule" className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="execution-mode">执行模式</Label>
|
||||
<Select defaultValue="immediate">
|
||||
<SelectTrigger id="execution-mode">
|
||||
<SelectValue placeholder="选择执行模式" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="immediate">立即执行</SelectItem>
|
||||
<SelectItem value="scheduled">定时执行</SelectItem>
|
||||
<SelectItem value="recurring">周期执行</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>计划执行时间</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn("w-full justify-start text-left font-normal", !date && "text-muted-foreground")}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, "PPP") : "选择日期"}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar mode="single" selected={date} onSelect={setDate} initialFocus />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="execution-time">执行时间</Label>
|
||||
<Input id="execution-time" type="time" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="recurrence-pattern">重复模式</Label>
|
||||
<Select disabled={true}>
|
||||
<SelectTrigger id="recurrence-pattern">
|
||||
<SelectValue placeholder="选择重复模式" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="daily">每天</SelectItem>
|
||||
<SelectItem value="weekly">每周</SelectItem>
|
||||
<SelectItem value="monthly">每月</SelectItem>
|
||||
<SelectItem value="custom">自定义</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="notify-completion" />
|
||||
<Label htmlFor="notify-completion">任务完成通知</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className="flex justify-end space-x-2 mt-6">
|
||||
<Button variant="outline">取消</Button>
|
||||
<Button>创建任务</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, 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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Pause, AlertCircle, CheckCircle, RefreshCw, Tag, User } from "lucide-react"
|
||||
|
||||
export function TaskExecution() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>任务执行状态</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">京东活跃用户标记</h3>
|
||||
<p className="text-sm text-muted-foreground">识别在京东平台活跃的用户并打上相应标签</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<Pause className="mr-2 h-4 w-4" />
|
||||
暂停
|
||||
</Button>
|
||||
<Button size="sm">
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
刷新状态
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<Badge className="bg-blue-100 text-blue-800 mr-2">运行中</Badge>
|
||||
<span className="text-sm text-muted-foreground">预计剩余时间: 45分钟</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">65%</span>
|
||||
</div>
|
||||
<Progress value={65} className="h-2" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<User className="h-5 w-5 text-blue-500" />
|
||||
<span className="text-sm font-medium">目标用户</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">12,500</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||||
<span className="text-sm font-medium">已处理</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">8,125</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tag className="h-5 w-5 text-purple-500" />
|
||||
<span className="text-sm font-medium">已打标签</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">5,840</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertCircle className="h-5 w-5 text-red-500" />
|
||||
<span className="text-sm font-medium">失败数</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">120</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="logs" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="logs">执行日志</TabsTrigger>
|
||||
<TabsTrigger value="users">用户处理</TabsTrigger>
|
||||
<TabsTrigger value="errors">错误记录</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="logs" className="space-y-4">
|
||||
<div className="bg-black text-green-400 p-4 rounded-md font-mono text-sm h-60 overflow-y-auto">
|
||||
<div>[2023-07-15 14:30:15] 任务开始执行</div>
|
||||
<div>[2023-07-15 14:30:16] 正在加载目标用户列表...</div>
|
||||
<div>[2023-07-15 14:30:20] 已加载 12,500 个目标用户</div>
|
||||
<div>[2023-07-15 14:30:25] 开始处理用户批次 #1 (1000 users)</div>
|
||||
<div>[2023-07-15 14:35:10] 批次 #1 处理完成: 成功 950, 失败 50</div>
|
||||
<div>[2023-07-15 14:35:15] 开始处理用户批次 #2 (1000 users)</div>
|
||||
<div>[2023-07-15 14:40:05] 批次 #2 处理完成: 成功 980, 失败 20</div>
|
||||
<div>[2023-07-15 14:40:10] 开始处理用户批次 #3 (1000 users)</div>
|
||||
<div>[2023-07-15 14:45:00] 批次 #3 处理完成: 成功 990, 失败 10</div>
|
||||
<div>[2023-07-15 14:45:05] 开始处理用户批次 #4 (1000 users)</div>
|
||||
<div>[2023-07-15 14:50:00] 批次 #4 处理完成: 成功 995, 失败 5</div>
|
||||
<div>[2023-07-15 14:50:05] 开始处理用户批次 #5 (1000 users)</div>
|
||||
<div>[2023-07-15 14:55:00] 批次 #5 处理完成: 成功 985, 失败 15</div>
|
||||
<div>[2023-07-15 14:55:05] 开始处理用户批次 #6 (1000 users)</div>
|
||||
<div>[2023-07-15 15:00:00] 批次 #6 处理完成: 成功 975, 失败 25</div>
|
||||
<div>[2023-07-15 15:00:05] 开始处理用户批次 #7 (1000 users)</div>
|
||||
<div>[2023-07-15 15:05:00] 批次 #7 处理完成: 成功 990, 失败 10</div>
|
||||
<div>[2023-07-15 15:05:05] 开始处理用户批次 #8 (1000 users)</div>
|
||||
<div>[2023-07-15 15:10:00] 批次 #8 处理完成: 成功 975, 失败 25</div>
|
||||
<div>[2023-07-15 15:10:05] 已处理 8000 个用户, 剩余 4500 个用户</div>
|
||||
<div>[2023-07-15 15:10:10] 开始处理用户批次 #9 (1000 users)</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="users" className="space-y-4">
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>用户ID</TableHead>
|
||||
<TableHead>处理时间</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>添加标签</TableHead>
|
||||
<TableHead>详情</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>user_12345</TableCell>
|
||||
<TableCell>2023-07-15 14:32:15</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>登录成功, 近30天活跃度高</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12346</TableCell>
|
||||
<TableCell>2023-07-15 14:32:18</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
电商偏好
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>登录成功, 购买频率高</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12347</TableCell>
|
||||
<TableCell>2023-07-15 14:32:20</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-red-100 text-red-800">失败</Badge>
|
||||
</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>登录失败, 账号异常</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12348</TableCell>
|
||||
<TableCell>2023-07-15 14:32:25</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>登录成功, 浏览时间长</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12349</TableCell>
|
||||
<TableCell>2023-07-15 14:32:30</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
高消费用户
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>登录成功, 消费金额高</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="errors" className="space-y-4">
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>错误ID</TableHead>
|
||||
<TableHead>时间</TableHead>
|
||||
<TableHead>用户ID</TableHead>
|
||||
<TableHead>错误类型</TableHead>
|
||||
<TableHead>错误详情</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>err_001</TableCell>
|
||||
<TableCell>2023-07-15 14:32:20</TableCell>
|
||||
<TableCell>user_12347</TableCell>
|
||||
<TableCell>登录失败</TableCell>
|
||||
<TableCell>账号异常, 可能被锁定</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>err_002</TableCell>
|
||||
<TableCell>2023-07-15 14:33:15</TableCell>
|
||||
<TableCell>user_12356</TableCell>
|
||||
<TableCell>网络错误</TableCell>
|
||||
<TableCell>连接超时, 请求失败</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>err_003</TableCell>
|
||||
<TableCell>2023-07-15 14:35:05</TableCell>
|
||||
<TableCell>user_12378</TableCell>
|
||||
<TableCell>数据错误</TableCell>
|
||||
<TableCell>用户数据格式不正确</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>err_004</TableCell>
|
||||
<TableCell>2023-07-15 14:38:30</TableCell>
|
||||
<TableCell>user_12390</TableCell>
|
||||
<TableCell>权限错误</TableCell>
|
||||
<TableCell>无权访问用户数据</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>err_005</TableCell>
|
||||
<TableCell>2023-07-15 14:42:10</TableCell>
|
||||
<TableCell>user_12405</TableCell>
|
||||
<TableCell>API限流</TableCell>
|
||||
<TableCell>请求频率超过限制</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent, 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
} from "recharts"
|
||||
import { Download, FileDown, Tag, User, CheckCircle, Clock } from "lucide-react"
|
||||
|
||||
export function TaskResults() {
|
||||
// 模拟数据
|
||||
const tagDistributionData = [
|
||||
{ name: "京东活跃用户", value: 5840 },
|
||||
{ name: "电商偏好", value: 4250 },
|
||||
{ name: "高消费用户", value: 1850 },
|
||||
{ name: "品质生活", value: 2100 },
|
||||
{ name: "数码爱好者", value: 1450 },
|
||||
]
|
||||
|
||||
const userActionData = [
|
||||
{ name: "登录成功", value: 8125 },
|
||||
{ name: "浏览商品", value: 6540 },
|
||||
{ name: "加入购物车", value: 3250 },
|
||||
{ name: "完成购买", value: 1850 },
|
||||
{ name: "评价商品", value: 980 },
|
||||
]
|
||||
|
||||
const timeDistributionData = [
|
||||
{ name: "0-5分钟", count: 2450 },
|
||||
{ name: "5-10分钟", count: 3650 },
|
||||
{ name: "10-20分钟", count: 2850 },
|
||||
{ name: "20-30分钟", count: 1950 },
|
||||
{ name: "30+分钟", count: 1600 },
|
||||
]
|
||||
|
||||
const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042", "#8884D8"]
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>任务结果分析</CardTitle>
|
||||
<Button variant="outline" size="sm">
|
||||
<FileDown className="mr-2 h-4 w-4" />
|
||||
导出报告
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">京东活跃用户标记</h3>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
<Badge className="bg-green-100 text-green-800">已完成</Badge>
|
||||
<span className="text-sm text-muted-foreground">完成时间: 2023-07-15 16:15:30</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<User className="h-5 w-5 text-blue-500" />
|
||||
<span className="text-sm font-medium">目标用户</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">12,500</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||||
<span className="text-sm font-medium">成功处理</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">12,380</span>
|
||||
<span className="text-sm text-muted-foreground ml-2">99.0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tag className="h-5 w-5 text-purple-500" />
|
||||
<span className="text-sm font-medium">打标签用户</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">8,450</span>
|
||||
<span className="text-sm text-muted-foreground ml-2">67.6%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock className="h-5 w-5 text-orange-500" />
|
||||
<span className="text-sm font-medium">执行时间</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-2xl font-bold">1h 45m</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="overview" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">结果概览</TabsTrigger>
|
||||
<TabsTrigger value="tags">标签分析</TabsTrigger>
|
||||
<TabsTrigger value="users">用户分析</TabsTrigger>
|
||||
<TabsTrigger value="details">详细数据</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-4">标签分布</h4>
|
||||
<div className="h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={tagDistributionData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{tagDistributionData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-4">用户行为分布</h4>
|
||||
<div className="h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={userActionData}
|
||||
margin={{
|
||||
top: 5,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="value" fill="#8884d8" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-4">用户停留时间分布</h4>
|
||||
<div className="h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={timeDistributionData}
|
||||
margin={{
|
||||
top: 5,
|
||||
right: 30,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="count" fill="#82ca9d" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tags" className="space-y-4">
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>标签名称</TableHead>
|
||||
<TableHead>用户数量</TableHead>
|
||||
<TableHead>占比</TableHead>
|
||||
<TableHead>标签分类</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">京东活跃用户</TableCell>
|
||||
<TableCell>5,840</TableCell>
|
||||
<TableCell>46.7%</TableCell>
|
||||
<TableCell>行为特征</TableCell>
|
||||
<TableCell>2023-07-15 15:30:25</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">电商偏好</TableCell>
|
||||
<TableCell>4,250</TableCell>
|
||||
<TableCell>34.0%</TableCell>
|
||||
<TableCell>偏好特征</TableCell>
|
||||
<TableCell>2023-07-15 15:35:10</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">高消费用户</TableCell>
|
||||
<TableCell>1,850</TableCell>
|
||||
<TableCell>14.8%</TableCell>
|
||||
<TableCell>价值特征</TableCell>
|
||||
<TableCell>2023-07-15 15:40:30</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">品质生活</TableCell>
|
||||
<TableCell>2,100</TableCell>
|
||||
<TableCell>16.8%</TableCell>
|
||||
<TableCell>偏好特征</TableCell>
|
||||
<TableCell>2023-07-15 15:45:15</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">数码爱好者</TableCell>
|
||||
<TableCell>1,450</TableCell>
|
||||
<TableCell>11.6%</TableCell>
|
||||
<TableCell>偏好特征</TableCell>
|
||||
<TableCell>2023-07-15 15:50:05</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="users" className="space-y-4">
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>用户分类</TableHead>
|
||||
<TableHead>数量</TableHead>
|
||||
<TableHead>占比</TableHead>
|
||||
<TableHead>平均停留时间</TableHead>
|
||||
<TableHead>平均交互次数</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">活跃购买用户</TableCell>
|
||||
<TableCell>3,250</TableCell>
|
||||
<TableCell>26.0%</TableCell>
|
||||
<TableCell>25分钟</TableCell>
|
||||
<TableCell>12.5</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">浏览未购买用户</TableCell>
|
||||
<TableCell>4,850</TableCell>
|
||||
<TableCell>38.8%</TableCell>
|
||||
<TableCell>15分钟</TableCell>
|
||||
<TableCell>8.2</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">短时访问用户</TableCell>
|
||||
<TableCell>2,450</TableCell>
|
||||
<TableCell>19.6%</TableCell>
|
||||
<TableCell>3分钟</TableCell>
|
||||
<TableCell>2.1</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">登录未浏览用户</TableCell>
|
||||
<TableCell>1,830</TableCell>
|
||||
<TableCell>14.6%</TableCell>
|
||||
<TableCell>1分钟</TableCell>
|
||||
<TableCell>0.5</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">登录失败用户</TableCell>
|
||||
<TableCell>120</TableCell>
|
||||
<TableCell>1.0%</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="details" className="space-y-4">
|
||||
<div className="flex justify-end mb-4">
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
导出详细数据
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>用户ID</TableHead>
|
||||
<TableHead>处理时间</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>添加标签</TableHead>
|
||||
<TableHead>停留时间</TableHead>
|
||||
<TableHead>交互次数</TableHead>
|
||||
<TableHead>详情</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>user_12345</TableCell>
|
||||
<TableCell>2023-07-15 14:32:15</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>18分钟</TableCell>
|
||||
<TableCell>9</TableCell>
|
||||
<TableCell>登录成功, 近30天活跃度高</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12346</TableCell>
|
||||
<TableCell>2023-07-15 14:32:18</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
电商偏好
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>25分钟</TableCell>
|
||||
<TableCell>15</TableCell>
|
||||
<TableCell>登录成功, 购买频率高</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12347</TableCell>
|
||||
<TableCell>2023-07-15 14:32:20</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-red-100 text-red-800">失败</Badge>
|
||||
</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>-</TableCell>
|
||||
<TableCell>登录失败, 账号异常</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12348</TableCell>
|
||||
<TableCell>2023-07-15 14:32:25</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>32分钟</TableCell>
|
||||
<TableCell>12</TableCell>
|
||||
<TableCell>登录成功, 浏览时间长</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>user_12349</TableCell>
|
||||
<TableCell>2023-07-15 14:32:30</TableCell>
|
||||
<TableCell>
|
||||
<Badge className="bg-green-100 text-green-800">成功</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
京东活跃用户
|
||||
</Badge>
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Tag className="h-3 w-3" />
|
||||
高消费用户
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>28分钟</TableCell>
|
||||
<TableCell>18</TableCell>
|
||||
<TableCell>登录成功, 消费金额高</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
74
components/ui/tooltip-help.tsx
Normal file
74
components/ui/tooltip-help.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { HelpCircle } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface TooltipHelpProps {
|
||||
content: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function TooltipHelp({ content, className = "" }: TooltipHelpProps) {
|
||||
const [isVisible, setIsVisible] = React.useState(false)
|
||||
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
||||
const tooltipRef = React.useRef<HTMLDivElement>(null)
|
||||
const timeoutRef = React.useRef<NodeJS.Timeout>()
|
||||
|
||||
const handleMouseEnter = (e: React.MouseEvent) => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
||||
const tooltipRect = tooltipRef.current?.getBoundingClientRect()
|
||||
|
||||
if (tooltipRect) {
|
||||
const top = rect.top - tooltipRect.height - 8
|
||||
const left = rect.left + (rect.width - tooltipRect.width) / 2
|
||||
|
||||
setPosition({ top, left })
|
||||
setIsVisible(true)
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
setIsVisible(false)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<HelpCircle
|
||||
className={cn("h-4 w-4 text-gray-400 hover:text-gray-600 cursor-help", className)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
/>
|
||||
{isVisible && (
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className="fixed z-50 px-3 py-2 text-xs text-white bg-gray-900 rounded-md shadow-lg max-w-xs"
|
||||
style={{
|
||||
top: position.top,
|
||||
left: position.left,
|
||||
transition: "opacity 150ms ease-in-out",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900"></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// 为了兼容性,导出一个简单的 TooltipProvider
|
||||
export const TooltipProvider = ({ children }: { children: React.ReactNode }) => <>{children}</>
|
||||
Reference in New Issue
Block a user