Files
users/app/tag-management/page.tsx
v0 2408d50cb0 refactor: overhaul UI for streamlined user experience
Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-07-18 13:47:12 +00:00

415 lines
15 KiB
TypeScript

"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Search, Plus, Filter, Download, Tag, Edit, Trash2, MoreHorizontal } from "lucide-react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { TagCategoryChart } from "@/components/tag-management/tag-category-chart"
import { TagUsageStats } from "@/components/tag-management/tag-usage-stats"
import { TagRelationshipGraph } from "@/components/tag-management/tag-relationship-graph"
interface TagItem {
id: string
name: string
category: string
type: "system" | "custom" | "derived"
source: string
coverage: number
createdAt: string
updatedAt: string
}
const mockTags: TagItem[] = [
{
id: "1",
name: "高价值用户",
category: "用户价值",
type: "derived",
source: "RFM模型",
coverage: 15.3,
createdAt: "2023-05-12",
updatedAt: "2023-07-15",
},
{
id: "2",
name: "90后",
category: "人口属性",
type: "system",
source: "注册信息",
coverage: 42.7,
createdAt: "2023-01-05",
updatedAt: "2023-01-05",
},
{
id: "3",
name: "游戏爱好者",
category: "兴趣爱好",
type: "custom",
source: "行为分析",
coverage: 28.4,
createdAt: "2023-04-18",
updatedAt: "2023-06-22",
},
{
id: "4",
name: "流失风险高",
category: "流失风险",
type: "derived",
source: "预测模型",
coverage: 8.9,
createdAt: "2023-06-30",
updatedAt: "2023-07-20",
},
{
id: "5",
name: "北京地区",
category: "地理位置",
type: "system",
source: "IP分析",
coverage: 12.5,
createdAt: "2023-02-15",
updatedAt: "2023-02-15",
},
{
id: "6",
name: "周末活跃",
category: "活跃时间",
type: "custom",
source: "行为分析",
coverage: 35.2,
createdAt: "2023-05-28",
updatedAt: "2023-07-10",
},
]
const tagCategories = ["用户价值", "人口属性", "兴趣爱好", "流失风险", "地理位置", "活跃时间", "消费能力", "渠道来源"]
export default function TagManagementPage() {
const [searchQuery, setSearchQuery] = useState("")
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [newTag, setNewTag] = useState({
name: "",
category: "",
type: "custom",
source: "",
})
const handleSelectAllTags = (checked: boolean) => {
if (checked) {
setSelectedTags(mockTags.map((tag) => tag.id))
} else {
setSelectedTags([])
}
}
const handleSelectTag = (tagId: string, checked: boolean) => {
if (checked) {
setSelectedTags([...selectedTags, tagId])
} else {
setSelectedTags(selectedTags.filter((id) => id !== tagId))
}
}
const handleCreateTag = () => {
// 这里应该是创建标签的逻辑
console.log("创建标签:", newTag)
setIsCreateDialogOpen(false)
setNewTag({
name: "",
category: "",
type: "custom",
source: "",
})
}
const filteredTags = mockTags.filter(
(tag) =>
tag.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
tag.category.toLowerCase().includes(searchQuery.toLowerCase()),
)
return (
<div className="container mx-auto p-6 space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground mt-1"></p>
</div>
<div className="flex space-x-2">
<Button variant="outline">
<Download className="mr-2 h-4 w-4" />
</Button>
<Button onClick={() => setIsCreateDialogOpen(true)}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center">
<Tag className="h-5 w-5 text-blue-500 mr-2" />
<div className="text-2xl font-bold">128</div>
</div>
<p className="text-xs text-muted-foreground mt-1"> 12 </p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">87.5%</div>
<p className="text-xs text-muted-foreground mt-1"> 2.3%</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">使</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">76.2%</div>
<p className="text-xs text-muted-foreground mt-1"> 5.1%</p>
</CardContent>
</Card>
</div>
<Tabs defaultValue="list" className="space-y-4">
<div className="flex justify-between items-center">
<TabsList>
<TabsTrigger value="list"></TabsTrigger>
<TabsTrigger value="analytics"></TabsTrigger>
<TabsTrigger value="relationships"></TabsTrigger>
</TabsList>
<div className="flex space-x-2">
<div className="relative w-64">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="搜索标签..."
className="pl-8"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
</div>
</div>
<TabsContent value="list" className="space-y-4">
<Card>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedTags.length === mockTags.length}
onCheckedChange={handleSelectAllTags}
/>
</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredTags.map((tag) => (
<TableRow key={tag.id}>
<TableCell>
<Checkbox
checked={selectedTags.includes(tag.id)}
onCheckedChange={(checked) => handleSelectTag(tag.id, !!checked)}
/>
</TableCell>
<TableCell className="font-medium">{tag.name}</TableCell>
<TableCell>{tag.category}</TableCell>
<TableCell>
<Badge
variant="outline"
className={
tag.type === "system"
? "bg-blue-50 text-blue-700 border-blue-200"
: tag.type === "derived"
? "bg-purple-50 text-purple-700 border-purple-200"
: "bg-green-50 text-green-700 border-green-200"
}
>
{tag.type === "system" ? "系统" : tag.type === "derived" ? "衍生" : "自定义"}
</Badge>
</TableCell>
<TableCell>{tag.source}</TableCell>
<TableCell>{tag.coverage}%</TableCell>
<TableCell>{tag.createdAt}</TableCell>
<TableCell>{tag.updatedAt}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Edit className="mr-2 h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuItem>
<Trash2 className="mr-2 h-4 w-4" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="analytics" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<TagCategoryChart />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
<CardDescription>使</CardDescription>
</CardHeader>
<CardContent>
<TagUsageStats />
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="relationships" className="space-y-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="h-[500px]">
<TagRelationshipGraph />
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 创建标签对话框 */}
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="tag-name" className="text-right">
</Label>
<Input
id="tag-name"
value={newTag.name}
onChange={(e) => setNewTag({ ...newTag, name: e.target.value })}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="tag-category" className="text-right">
</Label>
<Select value={newTag.category} onValueChange={(value) => setNewTag({ ...newTag, category: value })}>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="选择标签分类" />
</SelectTrigger>
<SelectContent>
{tagCategories.map((category) => (
<SelectItem key={category} value={category}>
{category}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="tag-type" className="text-right">
</Label>
<Select
value={newTag.type}
onValueChange={(value: "system" | "custom" | "derived") => setNewTag({ ...newTag, type: value })}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="选择标签类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="system"></SelectItem>
<SelectItem value="custom"></SelectItem>
<SelectItem value="derived"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="tag-source" className="text-right">
</Label>
<Input
id="tag-source"
value={newTag.source}
onChange={(e) => setNewTag({ ...newTag, source: e.target.value })}
className="col-span-3"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
</Button>
<Button onClick={handleCreateTag}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}