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>
This commit is contained in:
v0
2025-07-18 13:47:12 +00:00
parent 440b310c6f
commit 2408d50cb0
316 changed files with 55785 additions and 0 deletions

View File

@@ -0,0 +1,572 @@
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { ChevronLeft } from "lucide-react"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Check, ChevronsUpDown } from "lucide-react"
import { cn } from "@/app/lib/utils"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Switch } from "@/components/ui/switch"
import { Card } from "@/components/ui/card"
// 模拟标签数据
const tagOptions = [
{ value: "new_user", label: "新用户" },
{ value: "low_activity", label: "低活跃度" },
{ value: "high_spending", label: "高消费" },
{ value: "high_activity", label: "高活跃度" },
{ value: "potential", label: "潜在客户" },
{ value: "purchase_intent", label: "有购买意向" },
{ value: "holiday_consumer", label: "节日消费" },
{ value: "promotion_sensitive", label: "促销敏感" },
{ value: "tech_savvy", label: "科技爱好者" },
{ value: "luxury_buyer", label: "奢侈品买家" },
{ value: "price_sensitive", label: "价格敏感" },
{ value: "brand_loyal", label: "品牌忠诚" },
]
// 模拟区域数据
const regionOptions = [
{ value: "nationwide", label: "全国" },
{ value: "beijing", label: "北京" },
{ value: "shanghai", label: "上海" },
{ value: "guangzhou", label: "广州" },
{ value: "shenzhen", label: "深圳" },
{ value: "hangzhou", label: "杭州" },
{ value: "chengdu", label: "成都" },
{ value: "wuhan", label: "武汉" },
{ value: "east_china", label: "华东地区" },
{ value: "south_china", label: "华南地区" },
{ value: "north_china", label: "华北地区" },
{ value: "central_china", label: "华中地区" },
{ value: "tier_1", label: "一线城市" },
{ value: "tier_2", label: "二线城市" },
{ value: "tier_3", label: "三线城市" },
]
// 模拟设备数据
const deviceOptions = [
{ id: "1", name: "设备 A-001", status: "online" },
{ id: "2", name: "设备 B-002", status: "online" },
{ id: "3", name: "设备 C-003", status: "offline" },
{ id: "4", name: "设备 D-004", status: "online" },
{ id: "5", name: "设备 E-005", status: "online" },
]
// 模拟分发规则数据
const mockDistributionData = [
{
id: "1",
ruleType: "trafficPackage",
name: "普通流量包",
price: 0.5,
tags: ["new_user", "low_activity"],
regions: ["nationwide"],
deviceAddQuantity: 10,
selectAllDevices: true,
selectedDevices: [],
autoAdd: false,
},
{
id: "2",
ruleType: "distributionRule",
name: "高质量流量分发",
price: 2.5,
tags: ["high_spending", "high_activity"],
regions: ["tier_1"],
deviceAddQuantity: 5,
selectAllDevices: false,
selectedDevices: ["1", "4", "5"],
autoAdd: true,
},
]
type RuleType = "trafficPackage" | "distributionRule"
export default function EditDistributionPage({ params }: { params: { id: string } }) {
const router = useRouter()
const { id } = params
const [currentStep, setCurrentStep] = useState(1)
const [ruleType, setRuleType] = useState<RuleType>("trafficPackage")
const [name, setName] = useState("")
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [selectedRegions, setSelectedRegions] = useState<string[]>([])
const [selectedDevices, setSelectedDevices] = useState<string[]>([])
const [selectAllDevices, setSelectAllDevices] = useState(false)
const [deviceAddQuantity, setDeviceAddQuantity] = useState("10")
const [price, setPrice] = useState("")
const [tagsOpen, setTagsOpen] = useState(false)
const [regionsOpen, setRegionsOpen] = useState(false)
const [devicesOpen, setDevicesOpen] = useState(false)
const [autoAdd, setAutoAdd] = useState(false)
const [loading, setLoading] = useState(true)
useEffect(() => {
// 在实际应用中这里会从API获取数据
// 这里使用模拟数据
const distributionItem = mockDistributionData.find((item) => item.id === id)
if (distributionItem) {
setRuleType(distributionItem.ruleType as RuleType)
setName(distributionItem.name)
setSelectedTags(distributionItem.tags)
setSelectedRegions(distributionItem.regions)
setSelectAllDevices(distributionItem.selectAllDevices)
setSelectedDevices(distributionItem.selectedDevices)
setDeviceAddQuantity(distributionItem.deviceAddQuantity.toString())
setPrice(distributionItem.price.toString())
setAutoAdd(distributionItem.autoAdd)
} else {
// 如果找不到数据,返回列表页
router.push("/workspace/pricing")
}
setLoading(false)
}, [id, router])
// 当选择"全部设备"时,自动选中所有设备
const handleSelectAllDevices = (value: boolean) => {
setSelectAllDevices(value)
if (value) {
setSelectedDevices(deviceOptions.map((device) => device.id))
} else {
setSelectedDevices([])
}
}
const handleNext = () => {
if (name.trim()) {
setCurrentStep(2)
} else {
alert("请填写名称")
}
}
const handlePrevious = () => {
setCurrentStep(1)
}
const handleSubmit = () => {
// 在实际应用中这里会发送API请求更新数据
console.log({
id,
ruleType,
name,
tags: selectedTags,
regions: selectedRegions,
devices: selectAllDevices ? "all" : selectedDevices,
deviceAddQuantity: Number.parseInt(deviceAddQuantity),
price: Number.parseFloat(price),
autoAdd,
})
// 返回到列表页
router.push("/workspace/pricing")
}
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
</div>
)
}
return (
<div className="flex flex-col min-h-screen bg-gray-50">
{/* 顶部栏 */}
<header className="bg-white border-b border-gray-200 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<Button variant="ghost" size="icon" onClick={() => router.push("/workspace/pricing")} className="mr-2">
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="text-lg font-medium"></h1>
<div className="w-10"></div> {/* 占位,保持标题居中 */}
</div>
</header>
{/* 进度条 */}
<div className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center">
<div className="flex-1">
<div className="flex items-center">
<div
className={`rounded-full h-8 w-8 flex items-center justify-center ${
currentStep >= 1 ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-600"
}`}
>
1
</div>
<div className={`h-1 flex-1 mx-2 ${currentStep >= 2 ? "bg-blue-600" : "bg-gray-200"}`}></div>
<div
className={`rounded-full h-8 w-8 flex items-center justify-center ${
currentStep >= 2 ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-600"
}`}
>
2
</div>
</div>
</div>
</div>
<div className="flex mt-2">
<div className="flex-1 text-center text-sm font-medium"></div>
<div className="flex-1 text-center text-sm font-medium"></div>
</div>
</div>
</div>
{/* 主内容区 */}
<main className="flex-1 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{currentStep === 1 ? (
<div className="space-y-6">
<div className="space-y-4">
<h2 className="text-lg font-medium"></h2>
<RadioGroup
value={ruleType}
onValueChange={(value) => setRuleType(value as RuleType)}
className="grid grid-cols-1 md:grid-cols-2 gap-4"
>
<Card
className={`p-4 cursor-pointer border-2 ${ruleType === "trafficPackage" ? "border-blue-500" : "border-transparent"}`}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="trafficPackage" id="trafficPackage" />
<Label htmlFor="trafficPackage" className="cursor-pointer flex-1">
</Label>
</div>
<p className="mt-2 text-sm text-gray-500 pl-6"></p>
</Card>
<Card
className={`p-4 cursor-pointer border-2 ${ruleType === "distributionRule" ? "border-blue-500" : "border-transparent"}`}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="distributionRule" id="distributionRule" />
<Label htmlFor="distributionRule" className="cursor-pointer flex-1">
</Label>
</div>
<p className="mt-2 text-sm text-gray-500 pl-6"></p>
</Card>
</RadioGroup>
</div>
<div className="space-y-2">
<Label htmlFor="name">{ruleType === "trafficPackage" ? "流量包名称" : "分发规则名称"}</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={ruleType === "trafficPackage" ? "请输入流量包名称" : "请输入分发规则名称"}
required
/>
</div>
<div className="flex justify-end pt-4">
<Button type="button" onClick={handleNext}>
</Button>
</div>
</div>
) : (
<div className="space-y-6">
<div>
<h2 className="text-lg font-medium mb-4"></h2>
{/* 用户标签选择 */}
<div className="space-y-2 mb-6">
<Label></Label>
<Popover open={tagsOpen} onOpenChange={setTagsOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={tagsOpen}
className="w-full justify-between h-auto min-h-10"
>
{selectedTags.length > 0 ? (
<div className="flex flex-wrap gap-1 py-1">
{selectedTags.map((tag) => (
<Badge key={tag} variant="secondary" className="mr-1">
{tagOptions.find((t) => t.value === tag)?.label}
<button
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedTags(selectedTags.filter((t) => t !== tag))
}}
>
</button>
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground"></span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="搜索标签..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
<ScrollArea className="h-60">
{tagOptions.map((tag) => (
<CommandItem
key={tag.value}
value={tag.value}
onSelect={() => {
setSelectedTags(
selectedTags.includes(tag.value)
? selectedTags.filter((t) => t !== tag.value)
: [...selectedTags, tag.value],
)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedTags.includes(tag.value) ? "opacity-100" : "opacity-0",
)}
/>
{tag.label}
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
{/* 用户区域选择 */}
<div className="space-y-2 mb-6">
<Label></Label>
<Popover open={regionsOpen} onOpenChange={setRegionsOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={regionsOpen}
className="w-full justify-between h-auto min-h-10"
>
{selectedRegions.length > 0 ? (
<div className="flex flex-wrap gap-1 py-1">
{selectedRegions.map((region) => (
<Badge key={region} variant="outline" className="mr-1 bg-amber-50">
{regionOptions.find((r) => r.value === region)?.label}
<button
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedRegions(selectedRegions.filter((r) => r !== region))
}}
>
</button>
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground"></span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="搜索区域..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
<ScrollArea className="h-60">
{regionOptions.map((region) => (
<CommandItem
key={region.value}
value={region.value}
onSelect={() => {
setSelectedRegions(
selectedRegions.includes(region.value)
? selectedRegions.filter((r) => r !== region.value)
: [...selectedRegions, region.value],
)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedRegions.includes(region.value) ? "opacity-100" : "opacity-0",
)}
/>
{region.label}
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
{/* 设备设置 */}
<div className="space-y-4 mb-6">
<h3 className="font-medium"></h3>
<div className="flex items-center space-x-2">
<Switch id="selectAll" checked={selectAllDevices} onCheckedChange={handleSelectAllDevices} />
<Label htmlFor="selectAll"></Label>
</div>
{!selectAllDevices && (
<div className="space-y-2">
<Label></Label>
<Popover open={devicesOpen} onOpenChange={setDevicesOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={devicesOpen}
className="w-full justify-between h-auto min-h-10"
>
{selectedDevices.length > 0 ? (
<div className="flex flex-wrap gap-1 py-1">
{selectedDevices.map((deviceId) => (
<Badge key={deviceId} variant="outline" className="mr-1">
{deviceOptions.find((d) => d.id === deviceId)?.name}
<button
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedDevices(selectedDevices.filter((d) => d !== deviceId))
}}
>
</button>
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground"></span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="搜索设备..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
<ScrollArea className="h-60">
{deviceOptions.map((device) => (
<CommandItem
key={device.id}
value={device.id}
onSelect={() => {
setSelectedDevices(
selectedDevices.includes(device.id)
? selectedDevices.filter((d) => d !== device.id)
: [...selectedDevices, device.id],
)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedDevices.includes(device.id) ? "opacity-100" : "opacity-0",
)}
/>
{device.name}
<span
className={`ml-2 px-1.5 py-0.5 rounded text-xs ${
device.status === "online"
? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-800"
}`}
>
{device.status === "online" ? "在线" : "离线"}
</span>
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
)}
</div>
{/* 单设备添加数量 */}
<div className="space-y-2 mb-6">
<Label htmlFor="deviceAddQuantity"></Label>
<Input
id="deviceAddQuantity"
type="number"
min="1"
value={deviceAddQuantity}
onChange={(e) => setDeviceAddQuantity(e.target.value)}
required
/>
</div>
{/* 价格设置 */}
<div className="space-y-2 mb-6">
<Label htmlFor="price">/</Label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2">¥</span>
<Input
id="price"
type="number"
step="0.01"
min="0"
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="0.00"
className="pl-8"
required
/>
</div>
</div>
{/* 自动添加设置 */}
<div className="flex items-center space-x-2 mb-6">
<Switch id="autoAdd" checked={autoAdd} onCheckedChange={setAutoAdd} />
<Label htmlFor="autoAdd"></Label>
</div>
</div>
{/* 按钮组 */}
<div className="flex justify-between pt-4">
<Button type="button" variant="outline" onClick={handlePrevious}>
</Button>
<Button type="button" onClick={handleSubmit}>
</Button>
</div>
</div>
)}
</main>
</div>
)
}

View File

@@ -0,0 +1,509 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { ChevronLeft } from "lucide-react"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Check, ChevronsUpDown } from "lucide-react"
import { cn } from "@/app/lib/utils"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Switch } from "@/components/ui/switch"
import { Card } from "@/components/ui/card"
// 模拟标签数据
const tagOptions = [
{ value: "new_user", label: "新用户" },
{ value: "low_activity", label: "低活跃度" },
{ value: "high_spending", label: "高消费" },
{ value: "high_activity", label: "高活跃度" },
{ value: "potential", label: "潜在客户" },
{ value: "purchase_intent", label: "有购买意向" },
{ value: "holiday_consumer", label: "节日消费" },
{ value: "promotion_sensitive", label: "促销敏感" },
{ value: "tech_savvy", label: "科技爱好者" },
{ value: "luxury_buyer", label: "奢侈品买家" },
{ value: "price_sensitive", label: "价格敏感" },
{ value: "brand_loyal", label: "品牌忠诚" },
]
// 模拟区域数据
const regionOptions = [
{ value: "nationwide", label: "全国" },
{ value: "beijing", label: "北京" },
{ value: "shanghai", label: "上海" },
{ value: "guangzhou", label: "广州" },
{ value: "shenzhen", label: "深圳" },
{ value: "hangzhou", label: "杭州" },
{ value: "chengdu", label: "成都" },
{ value: "wuhan", label: "武汉" },
{ value: "east_china", label: "华东地区" },
{ value: "south_china", label: "华南地区" },
{ value: "north_china", label: "华北地区" },
{ value: "central_china", label: "华中地区" },
{ value: "tier_1", label: "一线城市" },
{ value: "tier_2", label: "二线城市" },
{ value: "tier_3", label: "三线城市" },
]
// 模拟设备数据
const deviceOptions = [
{ id: "1", name: "设备 A-001", status: "online" },
{ id: "2", name: "设备 B-002", status: "online" },
{ id: "3", name: "设备 C-003", status: "offline" },
{ id: "4", name: "设备 D-004", status: "online" },
{ id: "5", name: "设备 E-005", status: "online" },
]
type RuleType = "trafficPackage" | "distributionRule"
export default function NewDistributionPage() {
const router = useRouter()
const [currentStep, setCurrentStep] = useState(1)
const [ruleType, setRuleType] = useState<RuleType>("trafficPackage")
const [name, setName] = useState("")
const [selectedTags, setSelectedTags] = useState<string[]>([])
const [selectedRegions, setSelectedRegions] = useState<string[]>([])
const [selectedDevices, setSelectedDevices] = useState<string[]>([])
const [selectAllDevices, setSelectAllDevices] = useState(false)
const [deviceAddQuantity, setDeviceAddQuantity] = useState("10")
const [price, setPrice] = useState("")
const [tagsOpen, setTagsOpen] = useState(false)
const [regionsOpen, setRegionsOpen] = useState(false)
const [devicesOpen, setDevicesOpen] = useState(false)
const [autoAdd, setAutoAdd] = useState(false)
// 当选择"全部设备"时,自动选中所有设备
const handleSelectAllDevices = (value: boolean) => {
setSelectAllDevices(value)
if (value) {
setSelectedDevices(deviceOptions.map((device) => device.id))
} else {
setSelectedDevices([])
}
}
const handleNext = () => {
if (name.trim()) {
setCurrentStep(2)
} else {
alert("请填写名称")
}
}
const handlePrevious = () => {
setCurrentStep(1)
}
const handleSubmit = () => {
// 在实际应用中这里会发送API请求保存数据
console.log({
ruleType,
name,
tags: selectedTags,
regions: selectedRegions,
devices: selectAllDevices ? "all" : selectedDevices,
deviceAddQuantity: Number.parseInt(deviceAddQuantity),
price: Number.parseFloat(price),
autoAdd,
})
// 返回到列表页
router.push("/workspace/pricing")
}
return (
<div className="flex flex-col min-h-screen bg-gray-50">
{/* 顶部栏 */}
<header className="bg-white border-b border-gray-200 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<Button variant="ghost" size="icon" onClick={() => router.push("/workspace/pricing")} className="mr-2">
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="text-lg font-medium"></h1>
<div className="w-10"></div> {/* 占位,保持标题居中 */}
</div>
</header>
{/* 进度条 */}
<div className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center">
<div className="flex-1">
<div className="flex items-center">
<div
className={`rounded-full h-8 w-8 flex items-center justify-center ${
currentStep >= 1 ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-600"
}`}
>
1
</div>
<div className={`h-1 flex-1 mx-2 ${currentStep >= 2 ? "bg-blue-600" : "bg-gray-200"}`}></div>
<div
className={`rounded-full h-8 w-8 flex items-center justify-center ${
currentStep >= 2 ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-600"
}`}
>
2
</div>
</div>
</div>
</div>
<div className="flex mt-2">
<div className="flex-1 text-center text-sm font-medium"></div>
<div className="flex-1 text-center text-sm font-medium"></div>
</div>
</div>
</div>
{/* 主内容区 */}
<main className="flex-1 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{currentStep === 1 ? (
<div className="space-y-6">
<div className="space-y-4">
<h2 className="text-lg font-medium"></h2>
<RadioGroup
value={ruleType}
onValueChange={(value) => setRuleType(value as RuleType)}
className="grid grid-cols-1 md:grid-cols-2 gap-4"
>
<Card
className={`p-4 cursor-pointer border-2 ${ruleType === "trafficPackage" ? "border-blue-500" : "border-transparent"}`}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="trafficPackage" id="trafficPackage" />
<Label htmlFor="trafficPackage" className="cursor-pointer flex-1">
</Label>
</div>
<p className="mt-2 text-sm text-gray-500 pl-6"></p>
</Card>
<Card
className={`p-4 cursor-pointer border-2 ${ruleType === "distributionRule" ? "border-blue-500" : "border-transparent"}`}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="distributionRule" id="distributionRule" />
<Label htmlFor="distributionRule" className="cursor-pointer flex-1">
</Label>
</div>
<p className="mt-2 text-sm text-gray-500 pl-6"></p>
</Card>
</RadioGroup>
</div>
<div className="space-y-2">
<Label htmlFor="name">{ruleType === "trafficPackage" ? "流量包名称" : "分发规则名称"}</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={ruleType === "trafficPackage" ? "请输入流量包名称" : "请输入分发规则名称"}
required
/>
</div>
<div className="flex justify-end pt-4">
<Button type="button" onClick={handleNext}>
</Button>
</div>
</div>
) : (
<div className="space-y-6">
<div>
<h2 className="text-lg font-medium mb-4"></h2>
{/* 用户标签选择 */}
<div className="space-y-2 mb-6">
<Label></Label>
<Popover open={tagsOpen} onOpenChange={setTagsOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={tagsOpen}
className="w-full justify-between h-auto min-h-10"
>
{selectedTags.length > 0 ? (
<div className="flex flex-wrap gap-1 py-1">
{selectedTags.map((tag) => (
<Badge key={tag} variant="secondary" className="mr-1">
{tagOptions.find((t) => t.value === tag)?.label}
<button
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedTags(selectedTags.filter((t) => t !== tag))
}}
>
</button>
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground"></span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="搜索标签..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
<ScrollArea className="h-60">
{tagOptions.map((tag) => (
<CommandItem
key={tag.value}
value={tag.value}
onSelect={() => {
setSelectedTags(
selectedTags.includes(tag.value)
? selectedTags.filter((t) => t !== tag.value)
: [...selectedTags, tag.value],
)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedTags.includes(tag.value) ? "opacity-100" : "opacity-0",
)}
/>
{tag.label}
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
{/* 用户区域选择 */}
<div className="space-y-2 mb-6">
<Label></Label>
<Popover open={regionsOpen} onOpenChange={setRegionsOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={regionsOpen}
className="w-full justify-between h-auto min-h-10"
>
{selectedRegions.length > 0 ? (
<div className="flex flex-wrap gap-1 py-1">
{selectedRegions.map((region) => (
<Badge key={region} variant="outline" className="mr-1 bg-amber-50">
{regionOptions.find((r) => r.value === region)?.label}
<button
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedRegions(selectedRegions.filter((r) => r !== region))
}}
>
</button>
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground"></span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="搜索区域..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
<ScrollArea className="h-60">
{regionOptions.map((region) => (
<CommandItem
key={region.value}
value={region.value}
onSelect={() => {
setSelectedRegions(
selectedRegions.includes(region.value)
? selectedRegions.filter((r) => r !== region.value)
: [...selectedRegions, region.value],
)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedRegions.includes(region.value) ? "opacity-100" : "opacity-0",
)}
/>
{region.label}
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
{/* 设备设置 */}
<div className="space-y-4 mb-6">
<h3 className="font-medium"></h3>
<div className="flex items-center space-x-2">
<Switch id="selectAll" checked={selectAllDevices} onCheckedChange={handleSelectAllDevices} />
<Label htmlFor="selectAll"></Label>
</div>
{!selectAllDevices && (
<div className="space-y-2">
<Label></Label>
<Popover open={devicesOpen} onOpenChange={setDevicesOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={devicesOpen}
className="w-full justify-between h-auto min-h-10"
>
{selectedDevices.length > 0 ? (
<div className="flex flex-wrap gap-1 py-1">
{selectedDevices.map((deviceId) => (
<Badge key={deviceId} variant="outline" className="mr-1">
{deviceOptions.find((d) => d.id === deviceId)?.name}
<button
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedDevices(selectedDevices.filter((d) => d !== deviceId))
}}
>
</button>
</Badge>
))}
</div>
) : (
<span className="text-muted-foreground"></span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="搜索设备..." />
<CommandList>
<CommandEmpty></CommandEmpty>
<CommandGroup>
<ScrollArea className="h-60">
{deviceOptions.map((device) => (
<CommandItem
key={device.id}
value={device.id}
onSelect={() => {
setSelectedDevices(
selectedDevices.includes(device.id)
? selectedDevices.filter((d) => d !== device.id)
: [...selectedDevices, device.id],
)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedDevices.includes(device.id) ? "opacity-100" : "opacity-0",
)}
/>
{device.name}
<span
className={`ml-2 px-1.5 py-0.5 rounded text-xs ${
device.status === "online"
? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-800"
}`}
>
{device.status === "online" ? "在线" : "离线"}
</span>
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
)}
</div>
{/* 单设备添加数量 */}
<div className="space-y-2 mb-6">
<Label htmlFor="deviceAddQuantity"></Label>
<Input
id="deviceAddQuantity"
type="number"
min="1"
value={deviceAddQuantity}
onChange={(e) => setDeviceAddQuantity(e.target.value)}
required
/>
</div>
{/* 价格设置 */}
<div className="space-y-2 mb-6">
<Label htmlFor="price">/</Label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2">¥</span>
<Input
id="price"
type="number"
step="0.01"
min="0"
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="0.00"
className="pl-8"
required
/>
</div>
</div>
{/* 自动添加设置 */}
<div className="flex items-center space-x-2 mb-6">
<Switch id="autoAdd" checked={autoAdd} onCheckedChange={setAutoAdd} />
<Label htmlFor="autoAdd"></Label>
</div>
</div>
{/* 按钮组 */}
<div className="flex justify-between pt-4">
<Button type="button" variant="outline" onClick={handlePrevious}>
</Button>
<Button type="button" onClick={handleSubmit}>
</Button>
</div>
</div>
)}
</main>
</div>
)
}

View File

@@ -0,0 +1,116 @@
"use client"
import { useState } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { ChevronLeft, Edit, Trash2, Plus } from "lucide-react"
import { useRouter } from "next/navigation"
// 模拟定价数据
const mockPricingData = [
{
id: "1",
name: "普通流量包",
price: 0.5,
tags: ["新用户", "低活跃度"],
region: "全国",
},
{
id: "2",
name: "高质量流量",
price: 2.5,
tags: ["高消费", "高活跃度"],
region: "一线城市",
},
{
id: "3",
name: "精准营销流量",
price: 3.8,
tags: ["潜在客户", "有购买意向"],
region: "华东地区",
},
{
id: "4",
name: "节日促销流量",
price: 1.5,
tags: ["节日消费", "促销敏感"],
region: "全国",
},
]
export default function PricingPage() {
const router = useRouter()
const [pricingItems, setPricingItems] = useState(mockPricingData)
const handleDelete = (id: string) => {
setPricingItems(pricingItems.filter((item) => item.id !== id))
}
return (
<div className="flex flex-col min-h-screen bg-gray-50">
{/* 顶部栏 */}
<header className="bg-white border-b border-gray-200 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<Button variant="ghost" size="icon" onClick={() => router.push("/workspace")} className="mr-2">
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="text-lg font-medium"></h1>
<Button className="flex items-center gap-1" onClick={() => router.push("/workspace/pricing/new")}>
<Plus className="h-4 w-4" />
<span></span>
</Button>
</div>
</header>
{/* 主内容区 */}
<main className="flex-1 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="grid grid-cols-1 gap-4">
{pricingItems.length === 0 ? (
<div className="text-center py-12">
<p className="text-gray-500"></p>
</div>
) : (
pricingItems.map((item) => (
<Card key={item.id} className="p-4 hover:shadow-md transition-shadow">
<div className="flex justify-between items-start">
<div className="space-y-2">
<h3 className="font-medium text-lg">{item.name}</h3>
<div className="flex items-baseline gap-1">
<span className="text-2xl font-bold text-emerald-600">¥{item.price.toFixed(2)}</span>
<span className="text-gray-500 text-sm">/ </span>
</div>
<div className="flex flex-wrap gap-2 items-center mt-2">
{item.tags.map((tag, index) => (
<Badge key={index} variant="outline" className="bg-blue-50">
{tag}
</Badge>
))}
<Badge variant="outline" className="bg-amber-50">
{item.region}
</Badge>
</div>
</div>
<div className="flex gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => router.push(`/workspace/pricing/edit/${item.id}`)}
>
<Edit className="h-4 w-4 text-gray-500" />
</Button>
<Button variant="ghost" size="icon" onClick={() => handleDelete(item.id)}>
<Trash2 className="h-4 w-4 text-gray-500" />
</Button>
</div>
</div>
</Card>
))
)}
</div>
</main>
</div>
)
}