From 75d009b7220769d268f089b58ab841cfa7f5e3cd Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 29 May 2025 17:45:10 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E6=93=8D=E7=9B=98=E6=89=8B=E3=80=91?= =?UTF-8?q?=20=20=E5=B7=A5=E4=BD=9C=E5=8F=B0=E6=B5=81=E9=87=8F=E6=B1=A0?= =?UTF-8?q?=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../new/components/traffic-pool-step.tsx | 185 ++++++++++++------ Server/application/cunkebao/config/route.php | 1 + .../controller/WorkbenchController.php | 61 +++++- 3 files changed, 184 insertions(+), 63 deletions(-) diff --git a/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx b/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx index 22f5d310..de023f3c 100644 --- a/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx +++ b/Cunkebao/app/workspace/traffic-distribution/new/components/traffic-pool-step.tsx @@ -1,12 +1,15 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Checkbox } from "@/components/ui/checkbox" import { Search } from "lucide-react" import { Input } from "@/components/ui/input" import { Database } from "lucide-react" +import { api } from "@/lib/api" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog" interface TrafficPool { id: string @@ -19,43 +22,71 @@ interface TrafficPoolStepProps { onSubmit: (data: any) => void onBack: () => void initialData?: any + devices?: string[] } -export default function TrafficPoolStep({ onSubmit, onBack, initialData = {} }: TrafficPoolStepProps) { +export default function TrafficPoolStep({ onSubmit, onBack, initialData = {}, devices = [] }: TrafficPoolStepProps) { const [selectedPools, setSelectedPools] = useState(initialData.selectedPools || []) const [searchTerm, setSearchTerm] = useState("") const [isSubmitting, setIsSubmitting] = useState(false) - - // 模拟流量池数据 - const trafficPools: TrafficPool[] = [ - { id: "1", name: "新客流量池", count: 1250, description: "新获取的客户流量" }, - { id: "2", name: "高意向流量池", count: 850, description: "有购买意向的客户" }, - { id: "3", name: "复购流量池", count: 620, description: "已购买过产品的客户" }, - { id: "4", name: "活跃流量池", count: 1580, description: "近期活跃的客户" }, - { id: "5", name: "沉睡流量池", count: 2300, description: "长期未活跃的客户" }, - ] - - const filteredPools = trafficPools.filter( + const [deviceLabels, setDeviceLabels] = useState<{ label: string; count: number }[]>([]) + const [dialogOpen, setDialogOpen] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + const pageSize = 10 + const [total, setTotal] = useState(0) + const filteredPools = deviceLabels.filter( (pool) => - pool.name.toLowerCase().includes(searchTerm.toLowerCase()) || - pool.description.toLowerCase().includes(searchTerm.toLowerCase()), + pool.label && pool.label.toLowerCase().includes(searchTerm.toLowerCase()) ) + const totalPages = Math.ceil(total / pageSize) + const pagedPools = filteredPools.slice((currentPage - 1) * pageSize, currentPage * pageSize) - const togglePool = (id: string) => { - setSelectedPools((prev) => (prev.includes(id) ? prev.filter((poolId) => poolId !== id) : [...prev, id])) + // 监听 devices 变化,请求标签 + useEffect(() => { + if (!devices || devices.length === 0) { + setDeviceLabels([]) + setTotal(0) + return + } + const fetchLabels = async () => { + try { + const params = devices.join(",") + const res = await api.get<{ code: number; msg: string; data: { label: string; count: number }[]; total?: number }>(`/v1/workbench/device-labels?deviceIds=${params}`) + if (res.code === 200 && Array.isArray(res.data)) { + setDeviceLabels(res.data) + setTotal(res.total || res.data.length) + } else { + setDeviceLabels([]) + setTotal(0) + } + } catch (e) { + setDeviceLabels([]) + setTotal(0) + } + } + fetchLabels() + }, [devices]) + + // label 到描述的映射 + const poolDescMap: Record = { + "新客流量池": "新获取的客户流量", + "高意向流量池": "有购买意向的客户", + "复购流量池": "已购买过产品的客户", + "活跃流量池": "近期活跃的客户", + "沉睡流量池": "长期未活跃的客户", + } + + const togglePool = (label: string) => { + setSelectedPools((prev) => + prev.includes(label) ? prev.filter((id) => id !== label) : [...prev, label] + ) } const handleSubmit = async () => { setIsSubmitting(true) - try { - // 这里可以添加实际的提交逻辑 - await new Promise((resolve) => setTimeout(resolve, 1000)) // 模拟API请求 - - onSubmit({ - poolIds: selectedPools, - // 可以添加其他需要提交的数据 - }) + await new Promise((resolve) => setTimeout(resolve, 1000)) + onSubmit({ poolIds: selectedPools }) } catch (error) { console.error("提交失败:", error) } finally { @@ -63,52 +94,82 @@ export default function TrafficPoolStep({ onSubmit, onBack, initialData = {} }: } } + // 每次弹窗打开时重置分页 + useEffect(() => { if (dialogOpen) setCurrentPage(1) }, [dialogOpen]) + return (

流量池选择

-
-
- +
+ setSearchTerm(e.target.value)} - className="pl-10" + placeholder="选择流量池" + value={selectedPools.join(", ")} + readOnly + className="pl-10 cursor-pointer" + onClick={() => setDialogOpen(true)} />
- -
- {filteredPools.map((pool) => ( - togglePool(pool.id)} - > - -
-
- -
-
-

{pool.name}

-

{pool.description}

-
+ + + 选择流量池 +
+
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+
+ {pagedPools.map((pool) => ( + togglePool(pool.label)} + > +
+
+ +
+
+

{pool.label}

+

{poolDescMap[pool.label] || ""}

+
+
+
+ {pool.count} 人 + togglePool(pool.label)} + onClick={e => e.stopPropagation()} + /> +
+
+ ))} +
+ {/* 分页按钮 */} + {totalPages > 1 && ( +
+ + 第 {currentPage} / {totalPages} 页 +
-
- {pool.count} 人 - togglePool(pool.id)} - onClick={(e) => e.stopPropagation()} - /> -
- - - ))} -
- + )} +
+ +
+
+ +