Files
users/app/workspace/pricing/new/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

510 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
)
}