diff --git a/nkebao/src/pages/traffic-pool/detail/index.module.scss b/nkebao/src/pages/traffic-pool/detail/index.module.scss
new file mode 100644
index 00000000..84fa5f36
--- /dev/null
+++ b/nkebao/src/pages/traffic-pool/detail/index.module.scss
@@ -0,0 +1,354 @@
+.container {
+ padding: 16px;
+ background: #f5f5f5;
+ min-height: 100vh;
+}
+
+.notFound {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+}
+
+.notFoundText {
+ color: #999;
+ font-size: 16px;
+}
+
+.userCard {
+ margin-bottom: 16px;
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.userHeader {
+ display: flex;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 12px;
+}
+
+.userAvatar {
+ width: 64px;
+ height: 64px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.userInfo {
+ flex: 1;
+ min-width: 0;
+}
+
+.userName {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 4px;
+ line-height: 1.2;
+}
+
+.starIcon {
+ color: #ff4d4f;
+ font-size: 16px;
+}
+
+.userWechatId {
+ font-size: 14px;
+ color: #1677ff;
+ font-weight: 500;
+ margin-bottom: 8px;
+}
+
+.userTags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.rfmTags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.tabs {
+ background: #fff;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.tabContent {
+ padding: 16px;
+}
+
+.cardTitle {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 12px;
+}
+
+.infoCard {
+ margin-bottom: 16px;
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.infoItem {
+ padding: 8px 0;
+}
+
+.infoLabel {
+ font-size: 12px;
+ color: #666;
+ margin-bottom: 4px;
+}
+
+.infoValue {
+ font-size: 14px;
+ color: #333;
+ font-weight: 500;
+}
+
+.rfmCard {
+ margin-bottom: 16px;
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.rfmGrid {
+ display: flex;
+ justify-content: space-around;
+ text-align: center;
+}
+
+.rfmItem {
+ flex: 1;
+}
+
+.rfmValue {
+ font-size: 24px;
+ font-weight: bold;
+ line-height: 1;
+ margin-bottom: 4px;
+}
+
+.rfmLabel {
+ font-size: 12px;
+ color: #666;
+}
+
+.rfmItem:nth-child(1) .rfmValue {
+ color: #1677ff;
+}
+
+.rfmItem:nth-child(2) .rfmValue {
+ color: #52c41a;
+}
+
+.rfmItem:nth-child(3) .rfmValue {
+ color: #722ed1;
+}
+
+.poolButtons {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.statsCard {
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.statItem {
+ text-align: center;
+ padding: 8px 0;
+}
+
+.statValue {
+ font-size: 18px;
+ font-weight: bold;
+ line-height: 1;
+ margin-bottom: 4px;
+}
+
+.statLabel {
+ font-size: 12px;
+ color: #666;
+}
+
+.statItem:nth-child(1) .statValue {
+ color: #52c41a;
+}
+
+.statItem:nth-child(2) .statValue {
+ color: #1677ff;
+}
+
+.statItem:nth-child(3) .statValue {
+ color: #faad14;
+}
+
+.statItem:nth-child(4) .statValue {
+ color: #ff4d4f;
+}
+
+.interactionCard {
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.interactionList {
+ margin: 0;
+ padding: 0;
+}
+
+.interactionItem {
+ padding: 12px 0;
+ border-bottom: 1px solid #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+}
+
+.interactionIcon {
+ font-size: 20px;
+ color: #1677ff;
+ margin-right: 12px;
+}
+
+.interactionContent {
+ flex: 1;
+ min-width: 0;
+}
+
+.interactionTitle {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 4px;
+}
+
+.interactionDesc {
+ font-size: 12px;
+ color: #666;
+ margin-bottom: 4px;
+ line-height: 1.4;
+}
+
+.interactionValue {
+ color: #52c41a;
+ font-weight: bold;
+ margin-left: 4px;
+}
+
+.interactionTime {
+ font-size: 11px;
+ color: #999;
+}
+
+.emptyState {
+ text-align: center;
+ color: #999;
+ padding: 32px 0;
+ font-size: 14px;
+}
+
+.tagsCard {
+ margin-bottom: 16px;
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.tagsList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 16px;
+}
+
+.valueTags {
+ border-top: 1px solid #f0f0f0;
+ padding-top: 16px;
+}
+
+.valueTitle {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 12px;
+}
+
+.valueTagItem {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+}
+
+.rfmScore {
+ font-size: 12px;
+ color: #666;
+}
+
+.valueLabel {
+ font-size: 12px;
+ color: #666;
+}
+
+.addTagButton {
+ margin-top: 16px;
+}
+
+// 响应式设计
+@media (max-width: 375px) {
+ .container {
+ padding: 12px;
+ }
+
+ .userHeader {
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ }
+
+ .userAvatar {
+ width: 80px;
+ height: 80px;
+ }
+
+ .rfmGrid {
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .poolButtons {
+ flex-direction: column;
+ }
+
+ .statsCard {
+ .adm-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+ }
+}
diff --git a/nkebao/src/pages/traffic-pool/detail/index.tsx b/nkebao/src/pages/traffic-pool/detail/index.tsx
new file mode 100644
index 00000000..7389b966
--- /dev/null
+++ b/nkebao/src/pages/traffic-pool/detail/index.tsx
@@ -0,0 +1,469 @@
+import React, { useState } from "react";
+import { useParams } from "react-router-dom";
+import { Card, Tabs, Tag, Avatar, Button, List, Grid } from "antd-mobile";
+import {
+ UserOutline,
+ MobileOutline,
+ TeamOutline,
+ StarFill,
+ MessageOutline,
+ EyeOutline,
+ ClickOutline,
+ PayCircleOutline,
+} from "antd-mobile-icons";
+import Layout from "@/components/Layout/Layout";
+import NavCommon from "@/components/NavCommon";
+import styles from "./index.module.scss";
+
+// 复用类型定义和Mock数据生成函数
+import {
+ Device,
+ WechatAccount,
+ CustomerService,
+ TrafficPool,
+ RFMScore,
+ UserTag,
+ UserInteraction,
+ TrafficUser,
+ RFM_SEGMENTS,
+ generateMockDevices,
+ generateMockWechatAccounts,
+ generateMockCustomerServices,
+ generateMockTrafficPools,
+ generateRFMScore,
+ generateMockInteractions,
+ generateUserTags,
+} from "../list";
+
+const mockDevices = generateMockDevices();
+const mockWechatAccounts = generateMockWechatAccounts(mockDevices);
+const mockCustomerServices = generateMockCustomerServices();
+const mockTrafficPools = generateMockTrafficPools();
+
+// 生成Mock用户数据
+const generateMockUsers = (
+ devices: Device[],
+ wechatAccounts: WechatAccount[],
+ customerServices: CustomerService[],
+ trafficPools: TrafficPool[]
+): TrafficUser[] => {
+ return Array.from({ length: 500 }, (_, i) => {
+ const rfmScore = generateRFMScore();
+ const tags = generateUserTags(rfmScore);
+ const interactions = generateMockInteractions();
+
+ const user: TrafficUser = {
+ id: `user-${i + 1}`,
+ avatar: `/placeholder.svg?height=40&width=40&query=user${Math.floor(Math.random() * 100)}`,
+ nickname: `用户${i + 1}`,
+ wechatId: `wx_${Math.random().toString(36).substr(2, 8)}`,
+ phone: `1${Math.floor(Math.random() * 9) + 1}${Math.random().toString().substr(2, 9)}`,
+ region: ["北京", "上海", "广州", "深圳", "杭州", "成都"][
+ Math.floor(Math.random() * 6)
+ ],
+ note: Math.random() > 0.7 ? `这是用户${i + 1}的备注信息` : "",
+ status: ["pending", "added", "failed", "duplicate"][
+ Math.floor(Math.random() * 4)
+ ] as any,
+ addTime: new Date(
+ Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000
+ ).toISOString(),
+ source: "海报获客",
+ scenario: "poster",
+ deviceId: devices[Math.floor(Math.random() * devices.length)].id,
+ wechatAccountId:
+ wechatAccounts[Math.floor(Math.random() * wechatAccounts.length)].id,
+ customerServiceId:
+ customerServices[Math.floor(Math.random() * customerServices.length)]
+ .id,
+ poolIds:
+ Math.random() > 0.5
+ ? [trafficPools[Math.floor(Math.random() * trafficPools.length)].id]
+ : [],
+ tags,
+ rfmScore,
+ lastInteraction: new Date(
+ Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000
+ ).toISOString(),
+ totalSpent: Math.floor(Math.random() * 10000),
+ interactionCount: Math.floor(Math.random() * 50) + 1,
+ conversionRate: Math.floor(Math.random() * 100),
+ isDuplicate: Math.random() > 0.9,
+ mergedAccounts: [],
+ addStatus: ["not_added", "adding", "added", "failed"][
+ Math.floor(Math.random() * 4)
+ ] as any,
+ interactions,
+ };
+
+ return user;
+ });
+};
+
+const users = generateMockUsers(
+ mockDevices,
+ mockWechatAccounts,
+ mockCustomerServices,
+ mockTrafficPools
+);
+
+const TrafficPoolDetail: React.FC = () => {
+ const { id } = useParams();
+ const [activeTab, setActiveTab] = useState("base");
+
+ const user = users.find((u: TrafficUser) => u.id === id);
+
+ if (!user) {
+ return (
+
+
+
+ );
+ }
+
+ const wechatAccount = mockWechatAccounts.find(
+ (acc) => acc.id === user.wechatAccountId
+ );
+ const customerService = mockCustomerServices.find(
+ (cs) => cs.id === user.customerServiceId
+ );
+ const device = mockDevices.find((device) => device.id === user.deviceId);
+ const rfmSegment = Object.values(RFM_SEGMENTS).find(
+ (seg: any) => seg.name === user.rfmScore.segment
+ );
+
+ // 辅助函数
+ const getPoolNames = (poolIds: string[]) => {
+ return poolIds
+ .map((id) => mockTrafficPools.find((pool) => pool.id === id)?.name)
+ .filter(Boolean)
+ .join(", ");
+ };
+
+ const formatDate = (dateString: string) => {
+ if (!dateString) return "--";
+ try {
+ const date = new Date(dateString);
+ return date.toLocaleDateString("zh-CN");
+ } catch (error) {
+ return dateString;
+ }
+ };
+
+ const getInteractionIcon = (type: string) => {
+ switch (type) {
+ case "click":
+ return ;
+ case "message":
+ return ;
+ case "purchase":
+ return ;
+ case "view":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getInteractionTypeText = (type: string) => {
+ switch (type) {
+ case "click":
+ return "点击行为";
+ case "message":
+ return "消息互动";
+ case "purchase":
+ return "购买行为";
+ case "view":
+ return "页面浏览";
+ default:
+ return type;
+ }
+ };
+
+ const getStatusText = (status: string) => {
+ switch (status) {
+ case "added":
+ return "已添加";
+ case "pending":
+ return "未添加";
+ case "failed":
+ return "添加失败";
+ case "duplicate":
+ return "重复";
+ default:
+ return status;
+ }
+ };
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "added":
+ return "success";
+ case "pending":
+ return "default";
+ case "failed":
+ return "danger";
+ case "duplicate":
+ return "warning";
+ default:
+ return "default";
+ }
+ };
+
+ return (
+ }>
+
+ {/* 用户基本信息 */}
+
+
+
+ {user.nickname?.slice(0, 2) || "用户"}
+
+
+
+ {user.nickname}
+ {user.rfmScore.priority === "high" && (
+
+ )}
+
+
{user.wechatId}
+
+ {user.poolIds.length > 0 && (
+
+ {getPoolNames(user.poolIds)}
+
+ )}
+ {user.status === "added" && (
+
+ 优先添加
+
+ )}
+
+
+
+
+ {/* RFM标签 */}
+
+ {rfmSegment && (
+
+ {rfmSegment.name}
+
+ )}
+ {user.status === "added" && (
+
+ 优先添加
+
+ )}
+
+
+
+ {/* Tab导航 */}
+
+
+
+ {/* 关键信息卡片 */}
+
+ 关键信息
+
+
+
设备
+
+ {device?.name || "--"}
+
+
+
+
微信号
+
+ {wechatAccount?.nickname || "--"}
+
+
+
+
客服
+
+ {customerService?.name || "--"}
+
+
+
+
添加时间
+
+ {formatDate(user.addTime)}
+
+
+
+
最近互动
+
+ {formatDate(user.lastInteraction)}
+
+
+
+
+
+ {/* RFM评分卡片 */}
+
+ RFM评分
+
+
+
+ {user.rfmScore.recency}
+
+
最近性(R)
+
+
+
+ {user.rfmScore.frequency}
+
+
频率(F)
+
+
+
+ {user.rfmScore.monetary}
+
+
金额(M)
+
+
+
+
+ {/* 流量池按钮 */}
+
+
+
+
+
+ {/* 统计数据卡片 */}
+
+
+
+
¥{user.totalSpent}
+
总消费
+
+
+
+ {user.interactionCount}
+
+
互动次数
+
+
+
+ {user.conversionRate}%
+
+
转化率
+
+
+
+ {getStatusText(user.status)}
+
+
添加状态
+
+
+
+
+
+
+
+
+
+ 互动记录
+ {user.interactions && user.interactions.length > 0 ? (
+
+ {user.interactions.slice(0, 4).map((interaction) => (
+
+
+
+ {getInteractionTypeText(interaction.type)}
+
+
+ {interaction.content}
+ {interaction.type === "purchase" &&
+ interaction.value && (
+
+ ¥{interaction.value}
+
+ )}
+
+
+ {formatDate(interaction.timestamp)}{" "}
+ {new Date(interaction.timestamp).toLocaleTimeString(
+ "zh-CN",
+ {
+ hour: "2-digit",
+ minute: "2-digit",
+ }
+ )}
+
+
+
+ ))}
+
+ ) : (
+ 暂无互动记录
+ )}
+
+
+
+
+
+
+
+ 用户标签
+
+ {user.tags.map((tag) => (
+
+ {tag.name}
+
+ ))}
+
+
+
+
价值标签
+
+
+ 重要保持客户
+
+
+ RFM总分:
+ {user.rfmScore.recency +
+ user.rfmScore.frequency +
+ user.rfmScore.monetary}
+ /15
+
+
+
+ 价值等级:
+
+ 高价值
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default TrafficPoolDetail;
diff --git a/nkebao/src/pages/traffic-pool/list/index.module.scss b/nkebao/src/pages/traffic-pool/list/index.module.scss
new file mode 100644
index 00000000..6c5bf3c0
--- /dev/null
+++ b/nkebao/src/pages/traffic-pool/list/index.module.scss
@@ -0,0 +1,365 @@
+.container {
+ padding: 0;
+ background: #f5f5f5;
+ min-height: 100vh;
+}
+
+.headerActions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.spinning {
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.analyticsPanel {
+ background: #fff;
+ padding: 16px;
+ margin-bottom: 8px;
+}
+
+.statsGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.statCard {
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.statContent {
+ flex: 1;
+}
+
+.statValue {
+ font-size: 24px;
+ font-weight: bold;
+ color: #1677ff;
+ line-height: 1;
+ margin-bottom: 4px;
+}
+
+.statLabel {
+ font-size: 12px;
+ color: #666;
+}
+
+.statIcon {
+ font-size: 24px;
+ color: #1677ff;
+}
+
+.efficiencyCard {
+ padding: 16px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.efficiencyTitle {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 12px;
+}
+
+.efficiencyGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+ margin-bottom: 16px;
+}
+
+.efficiencyItem {
+ text-align: center;
+}
+
+.efficiencyValue {
+ font-size: 18px;
+ font-weight: bold;
+ color: #1677ff;
+ line-height: 1;
+ margin-bottom: 4px;
+}
+
+.efficiencyLabel {
+ font-size: 12px;
+ color: #666;
+}
+
+.statusGrid {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 8px;
+ padding-top: 12px;
+ border-top: 1px solid #f0f0f0;
+}
+
+.statusItem {
+ text-align: center;
+}
+
+.statusValue {
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 1;
+ margin-bottom: 4px;
+}
+
+.statusLabel {
+ font-size: 12px;
+ color: #666;
+}
+
+.statusItem:nth-child(1) .statusValue {
+ color: #52c41a;
+}
+
+.statusItem:nth-child(2) .statusValue {
+ color: #faad14;
+}
+
+.statusItem:nth-child(3) .statusValue {
+ color: #ff4d4f;
+}
+
+.searchSection {
+ background: #fff;
+ padding: 12px 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 8px;
+}
+
+.filterButton {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-size: 14px;
+}
+
+.actionBar {
+ background: #fff;
+ padding: 12px 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.selectSection {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.addButton {
+ font-size: 12px;
+ padding: 4px 8px;
+ height: 28px;
+}
+
+.totalCount {
+ font-size: 12px;
+ color: #666;
+}
+
+.userList {
+ background: #fff;
+ margin: 0;
+ padding: 0;
+}
+
+.userItem {
+ padding: 16px;
+ border-bottom: 1px solid #f0f0f0;
+ cursor: pointer;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: #fafafa;
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+}
+
+.userCheckbox {
+ margin-right: 12px;
+}
+
+.userContent {
+ flex: 1;
+ min-width: 0;
+}
+
+.userHeader {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ margin-bottom: 8px;
+}
+
+.userInfo {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+ min-width: 0;
+}
+
+.userAvatar {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.userDetails {
+ flex: 1;
+ min-width: 0;
+}
+
+.userName {
+ font-size: 16px;
+ font-weight: 500;
+ color: #333;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 4px;
+ line-height: 1.2;
+}
+
+.starIcon {
+ color: #ff4d4f;
+ font-size: 14px;
+}
+
+.userWechatId {
+ font-size: 14px;
+ color: #1677ff;
+ font-weight: 500;
+}
+
+.userMeta {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 8px;
+ font-size: 12px;
+ color: #666;
+}
+
+.metaItem {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.userTags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-bottom: 8px;
+}
+
+.poolInfo {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
+ color: #666;
+}
+
+.filterPopup {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+}
+
+.filterHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px;
+ border-bottom: 1px solid #f0f0f0;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.filterContent {
+ flex: 1;
+ padding: 16px;
+ overflow-y: auto;
+}
+
+.filterItem {
+ margin-bottom: 24px;
+}
+
+.filterLabel {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 8px;
+}
+
+.filterActions {
+ display: flex;
+ gap: 12px;
+ padding: 16px;
+ border-top: 1px solid #f0f0f0;
+ margin-top: auto;
+
+ .adm-button {
+ flex: 1;
+ }
+}
+
+// 响应式设计
+@media (max-width: 375px) {
+ .statsGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .efficiencyGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .statusGrid {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .userMeta {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+}
diff --git a/nkebao/src/pages/traffic-pool/list/index.tsx b/nkebao/src/pages/traffic-pool/list/index.tsx
new file mode 100644
index 00000000..f4d1da91
--- /dev/null
+++ b/nkebao/src/pages/traffic-pool/list/index.tsx
@@ -0,0 +1,943 @@
+import React, { useState, useEffect, useCallback } from "react";
+import { useNavigate } from "react-router-dom";
+import {
+ Card,
+ List,
+ Button,
+ SearchBar,
+ Checkbox,
+ Tag,
+ Avatar,
+ Toast,
+ SpinLoading,
+ Popup,
+ Selector,
+ InfiniteScroll,
+} from "antd-mobile";
+import {
+ SearchOutline,
+ FilterOutline,
+ RefreshOutline,
+ StarOutline,
+ StarFill,
+ UserOutline,
+ MobileOutline,
+ TeamOutline,
+ BarChartOutline,
+ ChevronDownOutline,
+ ChevronUpOutline,
+} from "antd-mobile-icons";
+import Layout from "@/components/Layout/Layout";
+import NavCommon from "@/components/NavCommon";
+import styles from "./index.module.scss";
+
+// 类型定义
+export interface Device {
+ id: string;
+ name: string;
+ status: "online" | "offline" | "busy";
+ battery: number;
+ location: string;
+ wechatAccounts: number;
+ dailyAddLimit: number;
+ todayAdded: number;
+}
+
+export interface WechatAccount {
+ id: string;
+ nickname: string;
+ wechatId: string;
+ avatar: string;
+ deviceId: string;
+ status: "normal" | "limited" | "blocked";
+ friendCount: number;
+ dailyAddLimit: number;
+}
+
+export interface CustomerService {
+ id: string;
+ name: string;
+ avatar: string;
+ status: "online" | "offline" | "busy";
+ assignedUsers: number;
+}
+
+export interface TrafficPool {
+ id: string;
+ name: string;
+ description: string;
+ userCount: number;
+ tags: string[];
+ createdAt: string;
+}
+
+export interface RFMScore {
+ recency: number;
+ frequency: number;
+ monetary: number;
+ total: number;
+ segment: string;
+ priority: "high" | "medium" | "low";
+}
+
+export interface UserTag {
+ id: string;
+ name: string;
+ color: string;
+ source: string;
+}
+
+export interface UserInteraction {
+ id: string;
+ type: "message" | "purchase" | "view" | "click";
+ content: string;
+ timestamp: string;
+ value?: number;
+}
+
+export interface TrafficUser {
+ id: string;
+ avatar: string;
+ nickname: string;
+ wechatId: string;
+ phone: string;
+ region: string;
+ note: string;
+ status: "pending" | "added" | "failed" | "duplicate";
+ addTime: string;
+ source: string;
+ scenario: string;
+ deviceId: string;
+ wechatAccountId: string;
+ customerServiceId: string;
+ poolIds: string[];
+ tags: UserTag[];
+ rfmScore: RFMScore;
+ lastInteraction: string;
+ totalSpent: number;
+ interactionCount: number;
+ conversionRate: number;
+ isDuplicate: boolean;
+ mergedAccounts: string[];
+ addStatus: "not_added" | "adding" | "added" | "failed";
+ interactions: UserInteraction[];
+}
+
+// 常量定义
+export const SCENARIOS = [
+ { id: "poster", name: "海报获客", icon: "🎨" },
+ { id: "phone", name: "电话获客", icon: "📞" },
+ { id: "douyin", name: "抖音获客", icon: "🎵" },
+ { id: "xiaohongshu", name: "小红书获客", icon: "📖" },
+ { id: "weixinqun", name: "微信群获客", icon: "👥" },
+ { id: "api", name: "API获客", icon: "🔗" },
+ { id: "order", name: "订单获客", icon: "📦" },
+ { id: "payment", name: "付款码获客", icon: "💳" },
+];
+
+export const RFM_SEGMENTS = {
+ "555": {
+ name: "重要价值客户",
+ color: "red",
+ icon: "👑",
+ priority: "high",
+ },
+ "554": {
+ name: "重要保持客户",
+ color: "purple",
+ icon: "💎",
+ priority: "high",
+ },
+ "544": {
+ name: "重要发展客户",
+ color: "blue",
+ icon: "🚀",
+ priority: "high",
+ },
+ "455": {
+ name: "重要挽留客户",
+ color: "orange",
+ icon: "⚠️",
+ priority: "medium",
+ },
+ "444": {
+ name: "一般价值客户",
+ color: "green",
+ icon: "👤",
+ priority: "medium",
+ },
+ "333": {
+ name: "一般保持客户",
+ color: "yellow",
+ icon: "📈",
+ priority: "medium",
+ },
+ "222": {
+ name: "新用户",
+ color: "cyan",
+ icon: "🌟",
+ priority: "low",
+ },
+ "111": {
+ name: "流失预警客户",
+ color: "gray",
+ icon: "😴",
+ priority: "low",
+ },
+} as const;
+
+// Mock数据生成函数
+const generateMockDevices = (): Device[] => {
+ return Array.from({ length: 8 }, (_, i) => ({
+ id: `device-${i + 1}`,
+ name: `设备${i + 1}`,
+ status: ["online", "offline", "busy"][Math.floor(Math.random() * 3)] as
+ | "online"
+ | "offline"
+ | "busy",
+ battery: Math.floor(Math.random() * 100),
+ location: ["北京", "上海", "广州", "深圳"][Math.floor(Math.random() * 4)],
+ wechatAccounts: Math.floor(Math.random() * 5) + 1,
+ dailyAddLimit: Math.random() > 0.5 ? 20 : 10,
+ todayAdded: Math.floor(Math.random() * 15),
+ }));
+};
+
+const generateMockWechatAccounts = (devices: Device[]): WechatAccount[] => {
+ const accounts: WechatAccount[] = [];
+ devices.forEach((device) => {
+ for (let i = 0; i < device.wechatAccounts; i++) {
+ accounts.push({
+ id: `wx-${device.id}-${i + 1}`,
+ nickname: `微信${device.id.split("-")[1]}-${i + 1}`,
+ wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
+ avatar: `/placeholder.svg?height=40&width=40&query=wx${Math.floor(Math.random() * 10)}`,
+ deviceId: device.id,
+ status: ["normal", "limited", "blocked"][
+ Math.floor(Math.random() * 3)
+ ] as "normal" | "limited" | "blocked",
+ friendCount: Math.floor(Math.random() * 4000) + 1000,
+ dailyAddLimit: Math.random() > 0.5 ? 20 : 10,
+ });
+ }
+ });
+ return accounts;
+};
+
+const generateMockCustomerServices = (): CustomerService[] => {
+ return Array.from({ length: 5 }, (_, i) => ({
+ id: `cs-${i + 1}`,
+ name: `客服${i + 1}`,
+ avatar: `/placeholder.svg?height=40&width=40&query=cs${i}`,
+ status: ["online", "offline", "busy"][Math.floor(Math.random() * 3)] as
+ | "online"
+ | "offline"
+ | "busy",
+ assignedUsers: Math.floor(Math.random() * 100) + 50,
+ }));
+};
+
+const generateMockTrafficPools = (): TrafficPool[] => {
+ return [
+ {
+ id: "pool-1",
+ name: "高价值客户池",
+ description: "包含所有高价值客户,优先添加",
+ userCount: 156,
+ tags: ["高价值", "优先添加", "重要客户"],
+ createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
+ },
+ {
+ id: "pool-2",
+ name: "潜在客户池",
+ description: "有潜力的用户,需要进一步培养",
+ userCount: 289,
+ tags: ["潜在客户", "需培养"],
+ createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000).toISOString(),
+ },
+ {
+ id: "pool-3",
+ name: "新用户池",
+ description: "新注册或新添加的用户",
+ userCount: 432,
+ tags: ["新用户", "待分类"],
+ createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
+ },
+ ];
+};
+
+const generateRFMScore = (): RFMScore => {
+ const recency = Math.floor(Math.random() * 5) + 1;
+ const frequency = Math.floor(Math.random() * 5) + 1;
+ const monetary = Math.floor(Math.random() * 5) + 1;
+ const total = recency + frequency + monetary;
+
+ let segment: string;
+ let priority: "high" | "medium" | "low";
+
+ if (total >= 12) {
+ segment = Object.values(RFM_SEGMENTS)[Math.floor(Math.random() * 3)].name;
+ priority = "high";
+ } else if (total >= 8) {
+ segment =
+ Object.values(RFM_SEGMENTS)[3 + Math.floor(Math.random() * 3)].name;
+ priority = "medium";
+ } else {
+ segment =
+ Object.values(RFM_SEGMENTS)[6 + Math.floor(Math.random() * 2)].name;
+ priority = "low";
+ }
+
+ return { recency, frequency, monetary, total, segment, priority };
+};
+
+const generateMockInteractions = (): UserInteraction[] => {
+ const types = ["message", "purchase", "view", "click"] as const;
+ return Array.from({ length: Math.floor(Math.random() * 10) + 1 }, (_, i) => {
+ const type = types[Math.floor(Math.random() * types.length)];
+ return {
+ id: `interaction-${i + 1}`,
+ type,
+ content:
+ type === "message"
+ ? "用户发送了消息"
+ : type === "purchase"
+ ? "用户购买了产品"
+ : type === "view"
+ ? "用户查看了产品"
+ : "用户点击了链接",
+ timestamp: new Date(
+ Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000
+ ).toISOString(),
+ value:
+ type === "purchase"
+ ? Math.floor(Math.random() * 1000) + 100
+ : undefined,
+ };
+ });
+};
+
+const generateUserTags = (rfmScore: RFMScore): UserTag[] => {
+ const allTags = [
+ { id: "tag-1", name: "活跃用户", color: "success", source: "system" },
+ { id: "tag-2", name: "高消费", color: "danger", source: "system" },
+ { id: "tag-3", name: "忠实客户", color: "primary", source: "system" },
+ { id: "tag-4", name: "新用户", color: "warning", source: "system" },
+ { id: "tag-5", name: "VIP客户", color: "purple", source: "manual" },
+ { id: "tag-6", name: "潜在客户", color: "default", source: "system" },
+ ];
+
+ const tags: UserTag[] = [];
+
+ if (rfmScore.priority === "high") {
+ tags.push(allTags[1], allTags[2]);
+ if (Math.random() > 0.5) tags.push(allTags[4]);
+ } else if (rfmScore.priority === "medium") {
+ tags.push(allTags[0]);
+ if (Math.random() > 0.5) tags.push(allTags[5]);
+ } else {
+ tags.push(allTags[3]);
+ if (Math.random() > 0.3) tags.push(allTags[5]);
+ }
+
+ return tags;
+};
+
+const mockDevices = generateMockDevices();
+const mockWechatAccounts = generateMockWechatAccounts(mockDevices);
+const mockCustomerServices = generateMockCustomerServices();
+const mockTrafficPools = generateMockTrafficPools();
+
+const generateMockUsers = (
+ devices: Device[],
+ wechatAccounts: WechatAccount[],
+ customerServices: CustomerService[],
+ trafficPools: TrafficPool[]
+): TrafficUser[] => {
+ return Array.from({ length: 500 }, (_, i) => {
+ const rfmScore = generateRFMScore();
+ const tags = generateUserTags(rfmScore);
+ const interactions = generateMockInteractions();
+
+ const user: TrafficUser = {
+ id: `user-${i + 1}`,
+ avatar: `/placeholder.svg?height=40&width=40&query=user${Math.floor(Math.random() * 100)}`,
+ nickname: `用户${i + 1}`,
+ wechatId: `wx_${Math.random().toString(36).substr(2, 8)}`,
+ phone: `1${Math.floor(Math.random() * 9) + 1}${Math.random().toString().substr(2, 9)}`,
+ region: ["北京", "上海", "广州", "深圳", "杭州", "成都"][
+ Math.floor(Math.random() * 6)
+ ],
+ note: Math.random() > 0.7 ? `这是用户${i + 1}的备注信息` : "",
+ status: ["pending", "added", "failed", "duplicate"][
+ Math.floor(Math.random() * 4)
+ ] as any,
+ addTime: new Date(
+ Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000
+ ).toISOString(),
+ source: SCENARIOS[Math.floor(Math.random() * SCENARIOS.length)].name,
+ scenario: SCENARIOS[Math.floor(Math.random() * SCENARIOS.length)].id,
+ deviceId: devices[Math.floor(Math.random() * devices.length)].id,
+ wechatAccountId:
+ wechatAccounts[Math.floor(Math.random() * wechatAccounts.length)].id,
+ customerServiceId:
+ customerServices[Math.floor(Math.random() * customerServices.length)]
+ .id,
+ poolIds:
+ Math.random() > 0.5
+ ? [trafficPools[Math.floor(Math.random() * trafficPools.length)].id]
+ : [],
+ tags,
+ rfmScore,
+ lastInteraction: new Date(
+ Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000
+ ).toISOString(),
+ totalSpent: Math.floor(Math.random() * 10000),
+ interactionCount: Math.floor(Math.random() * 50) + 1,
+ conversionRate: Math.floor(Math.random() * 100),
+ isDuplicate: Math.random() > 0.9,
+ mergedAccounts: [],
+ addStatus: ["not_added", "adding", "added", "failed"][
+ Math.floor(Math.random() * 4)
+ ] as any,
+ interactions,
+ };
+
+ return user;
+ });
+};
+
+const TrafficPoolList: React.FC = () => {
+ const navigate = useNavigate();
+
+ // 基础数据状态
+ const [users, setUsers] = useState([]);
+ const [devices] = useState(mockDevices);
+ const [wechatAccounts] = useState(mockWechatAccounts);
+ const [customerServices] = useState(mockCustomerServices);
+ const [trafficPools] = useState(mockTrafficPools);
+
+ // UI状态
+ const [loading, setLoading] = useState(false);
+ const [selectedUsers, setSelectedUsers] = useState([]);
+ const [showFilters, setShowFilters] = useState(false);
+ const [showAnalytics, setShowAnalytics] = useState(false);
+ const [hasMore, setHasMore] = useState(true);
+ const [page, setPage] = useState(1);
+
+ // 筛选状态
+ const [deviceFilter, setDeviceFilter] = useState("all");
+ const [poolFilter, setPoolFilter] = useState("all");
+ const [valuationFilter, setValuationFilter] = useState("all");
+ const [statusFilter, setStatusFilter] = useState("all");
+ const [searchQuery, setSearchQuery] = useState("");
+
+ // 初始化数据
+ useEffect(() => {
+ const mockUsers = generateMockUsers(
+ devices,
+ wechatAccounts,
+ customerServices,
+ trafficPools
+ );
+ setUsers(mockUsers);
+ }, [devices, wechatAccounts, customerServices, trafficPools]);
+
+ // 计算统计数据
+ const stats = {
+ total: users.length,
+ highValue: users.filter((u) => u.rfmScore.priority === "high").length,
+ mediumValue: users.filter((u) => u.rfmScore.priority === "medium").length,
+ lowValue: users.filter((u) => u.rfmScore.priority === "low").length,
+ duplicates: users.filter((u) => u.isDuplicate).length,
+ pending: users.filter((u) => u.status === "pending").length,
+ added: users.filter((u) => u.status === "added").length,
+ failed: users.filter((u) => u.status === "failed").length,
+ avgSpent: Math.round(
+ users.reduce((sum, u) => sum + u.totalSpent, 0) / users.length
+ ),
+ addSuccessRate: Math.round(
+ (users.filter((u) => u.status === "added").length / users.length) * 100
+ ),
+ duplicateRate: Math.round(
+ (users.filter((u) => u.isDuplicate).length / users.length) * 100
+ ),
+ };
+
+ // 过滤用户
+ const filteredUsers = users.filter((user) => {
+ const matchesSearch =
+ !searchQuery ||
+ user.nickname.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ user.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ user.phone.includes(searchQuery);
+
+ const matchesDevice =
+ deviceFilter === "all" || user.deviceId === deviceFilter;
+ const matchesValuation =
+ valuationFilter === "all" || user.rfmScore.priority === valuationFilter;
+ const matchesStatus =
+ statusFilter === "all" || user.status === statusFilter;
+ const matchesPool =
+ poolFilter === "all" ||
+ (poolFilter === "none"
+ ? user.poolIds.length === 0
+ : user.poolIds.includes(poolFilter));
+
+ return (
+ matchesSearch &&
+ matchesDevice &&
+ matchesValuation &&
+ matchesStatus &&
+ matchesPool
+ );
+ });
+
+ // 按优先级排序
+ const sortedUsers = filteredUsers.sort((a, b) => {
+ const priorityOrder = { high: 3, medium: 2, low: 1 };
+ return (
+ priorityOrder[b.rfmScore.priority] - priorityOrder[a.rfmScore.priority]
+ );
+ });
+
+ // 分页数据
+ const pageSize = 20;
+ const paginatedUsers = sortedUsers.slice(0, page * pageSize);
+
+ // 处理用户选择
+ const handleUserSelect = useCallback((userId: string, checked: boolean) => {
+ setSelectedUsers((prev) =>
+ checked ? [...prev, userId] : prev.filter((id) => id !== userId)
+ );
+ }, []);
+
+ // 处理全选
+ const handleSelectAll = useCallback(
+ (checked: boolean) => {
+ if (checked) {
+ setSelectedUsers(paginatedUsers.map((user) => user.id));
+ } else {
+ setSelectedUsers([]);
+ }
+ },
+ [paginatedUsers]
+ );
+
+ // 重置筛选器
+ const resetFilters = useCallback(() => {
+ setDeviceFilter("all");
+ setPoolFilter("all");
+ setValuationFilter("all");
+ setStatusFilter("all");
+ setSearchQuery("");
+ setShowFilters(false);
+ }, []);
+
+ // 刷新数据
+ const handleRefresh = useCallback(() => {
+ setLoading(true);
+ setTimeout(() => {
+ const refreshedUsers = generateMockUsers(
+ devices,
+ wechatAccounts,
+ customerServices,
+ trafficPools
+ );
+ setUsers(refreshedUsers);
+ setLoading(false);
+ Toast.show({ content: "刷新成功" });
+ }, 800);
+ }, [devices, wechatAccounts, customerServices, trafficPools]);
+
+ // 添加到流量池
+ const handleAddToPool = useCallback(() => {
+ if (selectedUsers.length === 0) {
+ Toast.show({ content: "请先选择要添加到流量池的用户" });
+ return;
+ }
+ Toast.show({ content: `已将 ${selectedUsers.length} 个用户添加到流量池` });
+ setSelectedUsers([]);
+ }, [selectedUsers.length]);
+
+ // 加载更多
+ const loadMore = async () => {
+ if (page * pageSize >= sortedUsers.length) {
+ setHasMore(false);
+ return;
+ }
+ setPage((prev) => prev + 1);
+ };
+
+ // 辅助函数
+ const getWechatAccount = (accountId: string) => {
+ return wechatAccounts.find((acc) => acc.id === accountId);
+ };
+
+ const getCustomerService = (csId: string) => {
+ return customerServices.find((cs) => cs.id === csId);
+ };
+
+ const getDevice = (deviceId: string) => {
+ return devices.find((device) => device.id === deviceId);
+ };
+
+ const getPoolNames = (poolIds: string[]) => {
+ return poolIds
+ .map((id) => trafficPools.find((pool) => pool.id === id)?.name)
+ .filter(Boolean)
+ .join(", ");
+ };
+
+ const formatDate = (dateString: string) => {
+ if (!dateString) return "--";
+ try {
+ const date = new Date(dateString);
+ return date.toLocaleDateString("zh-CN");
+ } catch (error) {
+ return dateString;
+ }
+ };
+
+ const getStatusText = (status: string) => {
+ switch (status) {
+ case "added":
+ return "已添加";
+ case "pending":
+ return "未添加";
+ case "failed":
+ return "添加失败";
+ case "duplicate":
+ return "重复";
+ default:
+ return status;
+ }
+ };
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "added":
+ return "success";
+ case "pending":
+ return "default";
+ case "failed":
+ return "danger";
+ case "duplicate":
+ return "warning";
+ default:
+ return "default";
+ }
+ };
+
+ return (
+
+
+
+
+ }
+ />
+ }
+ loading={loading}
+ >
+
+ {/* 数据分析面板 */}
+ {showAnalytics && (
+
+
+
+
+
{filteredUsers.length}
+
总用户数
+
+
+
+
+
+
{stats.highValue}
+
高价值用户
+
+
+
+
+
+ 添加效率
+
+
+
+ {stats.addSuccessRate}%
+
+
成功率
+
+
+
+ ¥{stats.avgSpent}
+
+
平均消费
+
+
+
+
+
+
{stats.pending}
+
待添加
+
+
+
{stats.failed}
+
添加失败
+
+
+
+
+ )}
+
+ {/* 搜索和筛选 */}
+
+
+
+
+
+ {/* 操作栏 */}
+
+
+ 0
+ }
+ onChange={handleSelectAll}
+ >
+ 全选
+
+ {selectedUsers.length > 0 && (
+
+ )}
+
+
+ 共 {filteredUsers.length} 个用户
+
+
+
+ {/* 用户列表 */}
+
+ {paginatedUsers.map((user) => {
+ const wechatAccount = getWechatAccount(user.wechatAccountId);
+ const customerService = getCustomerService(user.customerServiceId);
+ const device = getDevice(user.deviceId);
+
+ return (
+ navigate(`/traffic-pool/detail/${user.id}`)}
+ prefix={
+
+ handleUserSelect(user.id, checked)}
+ onClick={(e) => e.stopPropagation()}
+ />
+
+ }
+ arrow={false}
+ >
+
+
+
+
+ {user.nickname?.slice(0, 1) || "用户"}
+
+
+
+ {user.nickname}
+ {user.rfmScore.priority === "high" && (
+
+ )}
+
+
+ {user.wechatId}
+
+
+
+
+ {getStatusText(user.status)}
+
+
+
+
+
+
+ {device?.name || "设备0"}
+
+
+
+ {customerService?.name || "客服1"}
+
+
+
+
+ {user.tags.slice(0, 3).map((tag) => (
+
+ {tag.name}
+
+ ))}
+ {user.tags.length > 3 && (
+
+ +{user.tags.length - 3}
+
+ )}
+
+
+ {user.poolIds.length > 0 && (
+
+
+ {getPoolNames(user.poolIds)}
+
+ )}
+
+
+ );
+ })}
+
+
+ {/* 无限滚动 */}
+
+
+
+ {/* 筛选弹窗 */}
+ setShowFilters(false)}
+ position="right"
+ bodyStyle={{ width: "80vw" }}
+ >
+
+
+ 筛选选项
+
+
+
+
+
+
设备
+
({
+ label: `${device.name} - ${device.location}`,
+ value: device.id,
+ })),
+ ]}
+ value={[deviceFilter]}
+ onChange={(arr) => setDeviceFilter(arr[0])}
+ />
+
+
+
+
流量池
+
({
+ label: pool.name,
+ value: pool.id,
+ })),
+ ]}
+ value={[poolFilter]}
+ onChange={(arr) => setPoolFilter(arr[0])}
+ />
+
+
+
+
用户价值
+
setValuationFilter(arr[0])}
+ />
+
+
+
+
添加状态
+
setStatusFilter(arr[0])}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default TrafficPoolList;
diff --git a/nkebao/src/router/module/traffic-pool.tsx b/nkebao/src/router/module/traffic-pool.tsx
index d0dcd70d..99872ab7 100644
--- a/nkebao/src/router/module/traffic-pool.tsx
+++ b/nkebao/src/router/module/traffic-pool.tsx
@@ -1,5 +1,5 @@
-import TrafficPool from "@/pages/traffic-pool/TrafficPool";
-import TrafficPoolDetail from "@/pages/traffic-pool/TrafficPoolDetail";
+import TrafficPool from "@/pages/traffic-pool/list/index";
+import TrafficPoolDetail from "@/pages/traffic-pool/detail/index";
const trafficPoolRoutes = [
{