From 86ed3c4d265d6eb90a389d9212f77addacb6fcde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC=E9=87=8C=E7=9A=84=E6=B0=B8?= =?UTF-8?q?=E5=B9=B3?= Date: Thu, 24 Jul 2025 11:51:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B=20?= =?UTF-8?q?=E6=9A=82=E5=AD=98=E4=B8=80=E8=88=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/contact-import/ContactImport.tsx | 14 - .../{ => mine}/traffic-pool/TrafficPool.tsx | 0 .../traffic-pool/TrafficPoolDetail.tsx | 0 .../src/pages/mine/traffic-pool/detail/api.ts | 88 ++ .../traffic-pool/detail/index.module.scss | 0 .../pages/mine/traffic-pool/detail/index.tsx | 541 ++++++++++ .../src/pages/mine/traffic-pool/list/api.ts | 372 +++++++ .../traffic-pool/list/index.module.scss | 0 .../pages/mine/traffic-pool/list/index.tsx | 656 ++++++++++++ nkebao/src/pages/orders/Orders.tsx | 8 - nkebao/src/pages/plans/PlanDetail.tsx | 8 - nkebao/src/pages/plans/Plans.tsx | 10 - nkebao/src/pages/profile/Profile.tsx | 8 - .../src/pages/traffic-pool/detail/index.tsx | 469 --------- nkebao/src/pages/traffic-pool/list/index.tsx | 943 ------------------ nkebao/src/router/module/index.tsx | 25 - nkebao/src/router/module/mine.tsx | 59 ++ nkebao/src/router/module/other.tsx | 24 - nkebao/src/router/module/traffic-pool.tsx | 17 - nkebao/src/router/module/users.tsx | 23 - 20 files changed, 1716 insertions(+), 1549 deletions(-) delete mode 100644 nkebao/src/pages/contact-import/ContactImport.tsx rename nkebao/src/pages/{ => mine}/traffic-pool/TrafficPool.tsx (100%) rename nkebao/src/pages/{ => mine}/traffic-pool/TrafficPoolDetail.tsx (100%) create mode 100644 nkebao/src/pages/mine/traffic-pool/detail/api.ts rename nkebao/src/pages/{ => mine}/traffic-pool/detail/index.module.scss (100%) create mode 100644 nkebao/src/pages/mine/traffic-pool/detail/index.tsx create mode 100644 nkebao/src/pages/mine/traffic-pool/list/api.ts rename nkebao/src/pages/{ => mine}/traffic-pool/list/index.module.scss (100%) create mode 100644 nkebao/src/pages/mine/traffic-pool/list/index.tsx delete mode 100644 nkebao/src/pages/orders/Orders.tsx delete mode 100644 nkebao/src/pages/plans/PlanDetail.tsx delete mode 100644 nkebao/src/pages/plans/Plans.tsx delete mode 100644 nkebao/src/pages/profile/Profile.tsx delete mode 100644 nkebao/src/pages/traffic-pool/detail/index.tsx delete mode 100644 nkebao/src/pages/traffic-pool/list/index.tsx create mode 100644 nkebao/src/router/module/mine.tsx delete mode 100644 nkebao/src/router/module/traffic-pool.tsx delete mode 100644 nkebao/src/router/module/users.tsx diff --git a/nkebao/src/pages/contact-import/ContactImport.tsx b/nkebao/src/pages/contact-import/ContactImport.tsx deleted file mode 100644 index 9d03b6b2..00000000 --- a/nkebao/src/pages/contact-import/ContactImport.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const ContactImport: React.FC = () => { - return ( - - ); -}; - -export default ContactImport; diff --git a/nkebao/src/pages/traffic-pool/TrafficPool.tsx b/nkebao/src/pages/mine/traffic-pool/TrafficPool.tsx similarity index 100% rename from nkebao/src/pages/traffic-pool/TrafficPool.tsx rename to nkebao/src/pages/mine/traffic-pool/TrafficPool.tsx diff --git a/nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx b/nkebao/src/pages/mine/traffic-pool/TrafficPoolDetail.tsx similarity index 100% rename from nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx rename to nkebao/src/pages/mine/traffic-pool/TrafficPoolDetail.tsx diff --git a/nkebao/src/pages/mine/traffic-pool/detail/api.ts b/nkebao/src/pages/mine/traffic-pool/detail/api.ts new file mode 100644 index 00000000..6675194e --- /dev/null +++ b/nkebao/src/pages/mine/traffic-pool/detail/api.ts @@ -0,0 +1,88 @@ +import type { + Device, + WechatAccount, + CustomerService, + TrafficPool, + RFMScore, + UserTag, + UserInteraction, + TrafficUser, +} from "../list/api"; +import { + generateMockDevices, + generateMockWechatAccounts, + generateMockCustomerServices, + generateMockTrafficPools, + generateRFMScore, + generateUserTags, + generateMockInteractions, +} from "../list/api"; + +// 获取单个流量池用户详情(mock) +export function getTrafficUserDetail( + id: string +): Promise { + const devices = generateMockDevices(); + const wechatAccounts = generateMockWechatAccounts(devices); + const customerServices = generateMockCustomerServices(); + const trafficPools = generateMockTrafficPools(); + const users = Array.from({ length: 500 }, (_, i) => { + const rfmScore = generateRFMScore(); + const tags = generateUserTags(rfmScore); + const interactions = generateMockInteractions(); + return { + 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 Promise.resolve(users.find((u) => u.id === id)); +} + +// 获取meta信息 +export function getTrafficPoolMeta() { + return { + devices: generateMockDevices(), + wechatAccounts: generateMockWechatAccounts(generateMockDevices()), + customerServices: generateMockCustomerServices(), + trafficPools: generateMockTrafficPools(), + }; +} diff --git a/nkebao/src/pages/traffic-pool/detail/index.module.scss b/nkebao/src/pages/mine/traffic-pool/detail/index.module.scss similarity index 100% rename from nkebao/src/pages/traffic-pool/detail/index.module.scss rename to nkebao/src/pages/mine/traffic-pool/detail/index.module.scss diff --git a/nkebao/src/pages/mine/traffic-pool/detail/index.tsx b/nkebao/src/pages/mine/traffic-pool/detail/index.tsx new file mode 100644 index 00000000..e76eced9 --- /dev/null +++ b/nkebao/src/pages/mine/traffic-pool/detail/index.tsx @@ -0,0 +1,541 @@ +import React, { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { + Card, + Tabs, + Tag, + Avatar, + Button, + List, + Grid, + Toast, + SpinLoading, +} from "antd-mobile"; +import { + StarFilled, + MessageOutlined, + EyeOutlined, + MoreOutlined, + PayCircleOutlined, + PlusOutlined, + PhoneOutlined, + InboxOutlined, + UserOutlined, + CrownOutlined, + HeartOutlined, + ThunderboltOutlined, + ExclamationCircleOutlined, + RiseOutlined, + StopOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import MeauMobile from "@/components/MeauMobile/MeauMoible"; +import { + getTrafficUserDetail, + getTrafficPoolMeta, + type TrafficUser, + type Device, + type WechatAccount, + type CustomerService, + type TrafficPool, + SCENARIOS, + RFM_SEGMENTS, +} from "./api"; +import styles from "./index.module.scss"; + +const TrafficPoolDetail: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [meta, setMeta] = useState<{ + devices: Device[]; + wechatAccounts: WechatAccount[]; + customerServices: CustomerService[]; + trafficPools: TrafficPool[]; + }>({ + devices: [], + wechatAccounts: [], + customerServices: [], + trafficPools: [], + }); + + // 加载数据 + useEffect(() => { + if (id) { + loadData(); + } + }, [id]); + + const loadData = async () => { + try { + setLoading(true); + const [userData, metaData] = await Promise.all([ + getTrafficUserDetail(id!), + getTrafficPoolMeta(), + ]); + if (userData) { + setUser(userData); + } else { + Toast.show({ + content: "用户不存在", + position: "center", + }); + navigate(-1); + } + setMeta(metaData); + } catch (error) { + Toast.show({ + content: "加载数据失败", + position: "center", + }); + } finally { + setLoading(false); + } + }; + + // 获取状态标签 + const getStatusTag = (status: string) => { + const statusMap = { + pending: { text: "待添加", color: "warning" }, + added: { text: "已添加", color: "success" }, + failed: { text: "添加失败", color: "danger" }, + duplicate: { text: "重复用户", color: "default" }, + }; + const config = statusMap[status as keyof typeof statusMap]; + return {config.text}; + }; + + // 获取RFM分段图标 + const getRFMIcon = (segment: string) => { + const segmentConfig = Object.values(RFM_SEGMENTS).find( + (s) => s.name === segment + ); + if (!segmentConfig) return null; + + const iconMap = { + CrownOutlined: , + HeartOutlined: , + ThunderboltOutlined: , + ExclamationCircleOutlined: , + UserOutlined: , + RiseOutlined: , + StarFilled: , + StopOutlined: , + }; + + return iconMap[segmentConfig.icon as keyof typeof iconMap] || null; + }; + + // 获取场景图标 + const getScenarioIcon = (scenarioId: string) => { + const scenario = SCENARIOS.find((s) => s.id === scenarioId); + if (!scenario) return null; + + const iconMap = { + FileImageOutline: , + PhoneOutlined: , + PlayCircleOutlined: , + ReadOutlined: , + UsergroupAddOutlined: , + ApiOutlined: , + InboxOutlined: , + PayCircleOutlined: , + }; + + return iconMap[scenario.icon as keyof typeof iconMap] || null; + }; + + // 获取设备信息 + const getDevice = (deviceId: string) => { + return meta.devices.find((d) => d.id === deviceId); + }; + + // 获取微信号信息 + const getWechatAccount = (accountId: string) => { + return meta.wechatAccounts.find((w) => w.id === accountId); + }; + + // 获取客服信息 + const getCustomerService = (csId: string) => { + return meta.customerServices.find((c) => c.id === csId); + }; + + // 获取流量池信息 + const getTrafficPools = (poolIds: string[]) => { + return poolIds + .map((id) => meta.trafficPools.find((p) => p.id === id)) + .filter(Boolean); + }; + + // 格式化日期 + const formatDate = (dateString: string) => { + if (!dateString) return "--"; + try { + const date = new Date(dateString); + return date.toLocaleDateString("zh-CN"); + } catch (error) { + return dateString; + } + }; + + if (loading) { + return ( + +
+ +

加载中...

+
+
+ ); + } + + if (!user) { + return ( + +
+

用户不存在

+
+
+ ); + } + + return ( + navigate(-1)} + right={ + + } + /> + } + footer={} + > +
+ {/* 用户基本信息卡片 */} + +
+ +
+
+ {user.nickname} + {user.rfmScore.priority === "high" && ( + + )} +
+
{user.wechatId}
+
+ {getStatusTag(user.status)} + {user.tags.slice(0, 3).map((tag) => ( + + {tag.name} + + ))} + {user.tags.length > 3 && ( + + +{user.tags.length - 3} + + )} +
+
+
+ + + +
+
+ +
+
+
+ ¥{user.totalSpent.toLocaleString()} +
+
总消费
+
+
+
{user.interactionCount}
+
互动次数
+
+
+
{user.conversionRate}%
+
转化率
+
+
+
+ + {/* 详细信息标签页 */} + + +
+ + + } + /> + } + /> + } + /> + + } + /> + {user.note && ( + } + /> + )} + + + + +
RFM分析
+
+
+
+ {user.rfmScore.recency} +
+
最近购买
+
+
+
+ {user.rfmScore.frequency} +
+
购买频率
+
+
+
+ {user.rfmScore.monetary} +
+
购买金额
+
+
+
+ 客户分段: + + {getRFMIcon(user.rfmScore.segment)} + {user.rfmScore.segment} + +
+
+
+
+ + +
+ +
设备信息
+ {(() => { + const device = getDevice(user.deviceId); + return device ? ( +
+ +
+
{device.name}
+
+ {device.location} | 电量 {device.battery}% +
+ + {device.status === "online" ? "在线" : "离线"} + +
+
+ ) : ( +
未分配设备
+ ); + })()} +
+ + +
微信号
+ {(() => { + const wechatAccount = getWechatAccount(user.wechatAccountId); + return wechatAccount ? ( +
+ +
+
+ {wechatAccount.nickname} +
+
+ {wechatAccount.wechatId} | {wechatAccount.friendCount}{" "} + 好友 +
+ + {wechatAccount.status === "normal" ? "正常" : "受限"} + +
+
+ ) : ( +
未分配微信号
+ ); + })()} +
+ + +
客服
+ {(() => { + const customerService = getCustomerService( + user.customerServiceId + ); + return customerService ? ( +
+ +
+
+ {customerService.name} +
+
+ 已分配 {customerService.assignedUsers} 个用户 +
+ + {customerService.status === "online" + ? "在线" + : "离线"} + +
+
+ ) : ( +
未分配客服
+ ); + })()} +
+ + +
流量池
+ {(() => { + const pools = getTrafficPools(user.poolIds); + return pools.length > 0 ? ( +
+ {pools.map((pool) => ( +
+
{pool!.name}
+
+ {pool!.userCount} 个用户 | {pool!.description} +
+
+ {pool!.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+ ))} +
+ ) : ( +
未加入流量池
+ ); + })()} +
+
+
+ + +
+ +
最近互动
+ {user.interactions.length > 0 ? ( + + {user.interactions.slice(0, 10).map((interaction) => ( + + {interaction.type === "message" && ( + + )} + {interaction.type === "purchase" && ( + + )} + {interaction.type === "view" && } + {interaction.type === "click" && } +
+ } + extra={ + interaction.value && ( + + ¥{interaction.value} + + ) + } + /> + ))} + + ) : ( +
暂无互动记录
+ )} + +
+ + + + {/* 操作按钮 */} +
+ + + +
+ +
+ ); +}; + +export default TrafficPoolDetail; diff --git a/nkebao/src/pages/mine/traffic-pool/list/api.ts b/nkebao/src/pages/mine/traffic-pool/list/api.ts new file mode 100644 index 00000000..12155a51 --- /dev/null +++ b/nkebao/src/pages/mine/traffic-pool/list/api.ts @@ -0,0 +1,372 @@ +// 类型定义 +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[]; +} + +// 场景和RFM分段常量 +export const SCENARIOS = [ + { id: "poster", name: "海报获客", icon: "FileImageOutline" }, + { id: "phone", name: "电话获客", icon: "PhoneOutlined" }, + { id: "douyin", name: "抖音获客", icon: "PlayCircleOutlined" }, + { id: "xiaohongshu", name: "小红书获客", icon: "ReadOutlined" }, + { id: "weixinqun", name: "微信群获客", icon: "UsergroupAddOutlined" }, + { id: "api", name: "API获客", icon: "ApiOutlined" }, + { id: "order", name: "订单获客", icon: "InboxOutlined" }, + { id: "payment", name: "付款码获客", icon: "PayCircleOutlined" }, +]; + +export const RFM_SEGMENTS = { + "555": { + name: "重要价值客户", + color: "red", + icon: "CrownOutlined", + priority: "high", + }, + "554": { + name: "重要保持客户", + color: "purple", + icon: "HeartOutlined", + priority: "high", + }, + "544": { + name: "重要发展客户", + color: "blue", + icon: "ThunderboltOutlined", + priority: "high", + }, + "455": { + name: "重要挽留客户", + color: "orange", + icon: "ExclamationCircleOutlined", + priority: "medium", + }, + "444": { + name: "一般价值客户", + color: "green", + icon: "UserOutlined", + priority: "medium", + }, + "333": { + name: "一般保持客户", + color: "yellow", + icon: "RiseOutlined", + priority: "medium", + }, + "222": { + name: "新用户", + color: "cyan", + icon: "StarOutlined", + priority: "low", + }, + "111": { + name: "流失预警客户", + color: "gray", + icon: "StopOutlined", + priority: "low", + }, +} as const; + +// mock数据生成函数 +export function 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), + })); +} + +export function 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; +} + +export function 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, + })); +} + +export function 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(), + }, + ]; +} + +export function 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 }; +} + +export function 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, + }; + }); +} + +export function 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; +} + +// 获取流量池用户列表(mock) +export function getTrafficPoolUsers(): Promise { + const devices = generateMockDevices(); + const wechatAccounts = generateMockWechatAccounts(devices); + const customerServices = generateMockCustomerServices(); + const trafficPools = generateMockTrafficPools(); + const users = Array.from({ length: 500 }, (_, i) => { + const rfmScore = generateRFMScore(); + const tags = generateUserTags(rfmScore); + const interactions = generateMockInteractions(); + return { + 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 Promise.resolve(users); +} + +// 获取设备、微信号、客服、流量池 +export function getTrafficPoolMeta() { + return { + devices: generateMockDevices(), + wechatAccounts: generateMockWechatAccounts(generateMockDevices()), + customerServices: generateMockCustomerServices(), + trafficPools: generateMockTrafficPools(), + }; +} diff --git a/nkebao/src/pages/traffic-pool/list/index.module.scss b/nkebao/src/pages/mine/traffic-pool/list/index.module.scss similarity index 100% rename from nkebao/src/pages/traffic-pool/list/index.module.scss rename to nkebao/src/pages/mine/traffic-pool/list/index.module.scss diff --git a/nkebao/src/pages/mine/traffic-pool/list/index.tsx b/nkebao/src/pages/mine/traffic-pool/list/index.tsx new file mode 100644 index 00000000..25de91e5 --- /dev/null +++ b/nkebao/src/pages/mine/traffic-pool/list/index.tsx @@ -0,0 +1,656 @@ +import React, { useState, useEffect, useMemo } 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 { + SettingOutlined, + ReloadOutlined, + StarFilled, + UserOutlined, + EyeOutlined, + MessageOutlined, + PhoneOutlined, + InboxOutlined, + PayCircleOutlined, + CrownOutlined, + HeartOutlined, + ThunderboltOutlined, + ExclamationCircleOutlined, + RiseOutlined, + StopOutlined, + DownOutlined, + UpOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import MeauMobile from "@/components/MeauMobile/MeauMoible"; +import { + getTrafficPoolUsers, + getTrafficPoolMeta, + type TrafficUser, + type Device, + type WechatAccount, + type CustomerService, + type TrafficPool, + SCENARIOS, + RFM_SEGMENTS, +} from "./api"; +import styles from "./index.module.scss"; + +const TrafficPoolList: React.FC = () => { + const navigate = useNavigate(); + const [users, setUsers] = useState([]); + const [filteredUsers, setFilteredUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [hasMore, setHasMore] = useState(true); + const [page, setPage] = useState(1); + const [searchText, setSearchText] = useState(""); + const [selectedUsers, setSelectedUsers] = useState([]); + const [showFilters, setShowFilters] = useState(false); + const [showScenarios, setShowScenarios] = useState(false); + const [showRFM, setShowRFM] = useState(false); + const [showDevices, setShowDevices] = useState(false); + const [showWechatAccounts, setShowWechatAccounts] = useState(false); + const [showCustomerServices, setShowCustomerServices] = useState(false); + const [showTrafficPools, setShowTrafficPools] = useState(false); + + // 筛选状态 + const [filters, setFilters] = useState({ + status: [] as string[], + scenario: [] as string[], + rfmSegment: [] as string[], + device: [] as string[], + wechatAccount: [] as string[], + customerService: [] as string[], + trafficPool: [] as string[], + }); + + // Meta数据 + const [meta, setMeta] = useState<{ + devices: Device[]; + wechatAccounts: WechatAccount[]; + customerServices: CustomerService[]; + trafficPools: TrafficPool[]; + }>({ + devices: [], + wechatAccounts: [], + customerServices: [], + trafficPools: [], + }); + + // 加载数据 + useEffect(() => { + loadData(); + }, []); + + const loadData = async () => { + try { + setLoading(true); + const [usersData, metaData] = await Promise.all([ + getTrafficPoolUsers(), + getTrafficPoolMeta(), + ]); + setUsers(usersData); + setFilteredUsers(usersData); + setMeta(metaData); + } catch (error) { + Toast.show({ + content: "加载数据失败", + position: "center", + }); + } finally { + setLoading(false); + } + }; + + // 筛选用户 + useEffect(() => { + let filtered = users; + + // 搜索筛选 + if (searchText) { + filtered = filtered.filter( + (user) => + user.nickname.includes(searchText) || + user.wechatId.includes(searchText) || + user.phone.includes(searchText) || + user.note.includes(searchText) + ); + } + + // 状态筛选 + if (filters.status.length > 0) { + filtered = filtered.filter((user) => + filters.status.includes(user.status) + ); + } + + // 场景筛选 + if (filters.scenario.length > 0) { + filtered = filtered.filter((user) => + filters.scenario.includes(user.scenario) + ); + } + + // RFM分段筛选 + if (filters.rfmSegment.length > 0) { + filtered = filtered.filter((user) => + filters.rfmSegment.includes(user.rfmScore.segment) + ); + } + + // 设备筛选 + if (filters.device.length > 0) { + filtered = filtered.filter((user) => + filters.device.includes(user.deviceId) + ); + } + + // 微信号筛选 + if (filters.wechatAccount.length > 0) { + filtered = filtered.filter((user) => + filters.wechatAccount.includes(user.wechatAccountId) + ); + } + + // 客服筛选 + if (filters.customerService.length > 0) { + filtered = filtered.filter((user) => + filters.customerService.includes(user.customerServiceId) + ); + } + + // 流量池筛选 + if (filters.trafficPool.length > 0) { + filtered = filtered.filter((user) => + user.poolIds.some((poolId) => filters.trafficPool.includes(poolId)) + ); + } + + setFilteredUsers(filtered); + }, [users, searchText, filters]); + + // 加载更多 + const loadMore = async () => { + if (!hasMore) return; + setPage((prev) => prev + 1); + // 这里可以调用分页API + setHasMore(false); + }; + + // 全选/取消全选 + const toggleSelectAll = () => { + if (selectedUsers.length === filteredUsers.length) { + setSelectedUsers([]); + } else { + setSelectedUsers(filteredUsers.map((user) => user.id)); + } + }; + + // 选择单个用户 + const toggleSelectUser = (userId: string) => { + setSelectedUsers((prev) => + prev.includes(userId) + ? prev.filter((id) => id !== userId) + : [...prev, userId] + ); + }; + + // 批量操作 + const handleBatchOperation = (operation: string) => { + if (selectedUsers.length === 0) { + Toast.show({ + content: "请先选择用户", + position: "center", + }); + return; + } + + Toast.show({ + content: `${operation} ${selectedUsers.length} 个用户`, + position: "center", + }); + }; + + // 获取状态标签 + const getStatusTag = (status: string) => { + const statusMap = { + pending: { text: "待添加", color: "warning" }, + added: { text: "已添加", color: "success" }, + failed: { text: "添加失败", color: "danger" }, + duplicate: { text: "重复用户", color: "default" }, + }; + const config = statusMap[status as keyof typeof statusMap]; + return {config.text}; + }; + + // 获取RFM分段图标 + const getRFMIcon = (segment: string) => { + const segmentConfig = Object.values(RFM_SEGMENTS).find( + (s) => s.name === segment + ); + if (!segmentConfig) return null; + + const iconMap = { + CrownOutlined: , + HeartOutlined: , + ThunderboltOutlined: , + ExclamationCircleOutlined: , + UserOutlined: , + RiseOutlined: , + StarFilled: , + StopOutlined: , + }; + + return iconMap[segmentConfig.icon as keyof typeof iconMap] || null; + }; + + // 获取场景图标 + const getScenarioIcon = (scenarioId: string) => { + const scenario = SCENARIOS.find((s) => s.id === scenarioId); + if (!scenario) return null; + + const iconMap = { + FileImageOutline: , + PhoneOutlined: , + PlayCircleOutlined: , + ReadOutlined: , + UsergroupAddOutlined: , + ApiOutlined: , + InboxOutlined: , + PayCircleOutlined: , + }; + + return iconMap[scenario.icon as keyof typeof iconMap] || null; + }; + + // 统计信息 + const stats = useMemo(() => { + const total = filteredUsers.length; + const pending = filteredUsers.filter((u) => u.status === "pending").length; + const added = filteredUsers.filter((u) => u.status === "added").length; + const failed = filteredUsers.filter((u) => u.status === "failed").length; + const duplicate = filteredUsers.filter( + (u) => u.status === "duplicate" + ).length; + + return { total, pending, added, failed, duplicate }; + }, [filteredUsers]); + + if (loading) { + return ( + +
+ +

加载中...

+
+
+ ); + } + + return ( + navigate(-1)} + right={ + + } + /> + } + footer={} + > +
+ {/* 统计卡片 */} + +
+
+
{stats.total}
+
总用户
+
+
+
{stats.pending}
+
待添加
+
+
+
{stats.added}
+
已添加
+
+
+
{stats.failed}
+
失败
+
+
+
+ + {/* 搜索和操作栏 */} +
+ + +
+ + {/* 批量操作栏 */} + {selectedUsers.length > 0 && ( + +
+ 已选择 {selectedUsers.length} 个用户 +
+
+ + + +
+
+ )} + + {/* 用户列表 */} + + + 0 && + selectedUsers.length < filteredUsers.length + } + /> + + {selectedUsers.length > 0 + ? `已选择 ${selectedUsers.length} 个用户` + : "全选"} + + + + {filteredUsers.map((user) => ( + navigate(`/traffic-pool/${user.id}`)} + > +
+ toggleSelectUser(user.id)} + onClick={(e) => e.stopPropagation()} + /> + +
+
+ {user.nickname} + {user.isDuplicate && ( + + 重复 + + )} +
+
+ {user.wechatId} + {user.phone} + {user.region} +
+
+ {getStatusTag(user.status)} + {user.tags.slice(0, 2).map((tag) => ( + + {tag.name} + + ))} + {user.tags.length > 2 && ( + + +{user.tags.length - 2} + + )} +
+
+
+ +
+
+ RFM: + + {getRFMIcon(user.rfmScore.segment)} + {user.rfmScore.segment} + +
+
+ 消费: + + ¥{user.totalSpent.toLocaleString()} + +
+
+ 互动: + + {user.interactionCount}次 + +
+
+ +
+ + + +
+
+ ))} +
+ + {/* 无限滚动 */} + + + {/* 筛选弹窗 */} + setShowFilters(false)} + position="right" + bodyStyle={{ width: "80vw" }} + > +
+
+

筛选条件

+ +
+ +
+

用户状态

+ + setFilters((prev) => ({ ...prev, status: value })) + } + /> +
+ +
+

获客场景

+ ({ + label: s.name, + value: s.id, + }))} + value={filters.scenario} + onChange={(value) => + setFilters((prev) => ({ ...prev, scenario: value })) + } + /> +
+ +
+

RFM分段

+ ({ + label: s.name, + value: s.name, + }))} + value={filters.rfmSegment} + onChange={(value) => + setFilters((prev) => ({ ...prev, rfmSegment: value })) + } + /> +
+ +
+

设备

+ ({ + label: d.name, + value: d.id, + }))} + value={filters.device} + onChange={(value) => + setFilters((prev) => ({ ...prev, device: value })) + } + /> +
+ +
+

微信号

+ ({ + label: w.nickname, + value: w.id, + }))} + value={filters.wechatAccount} + onChange={(value) => + setFilters((prev) => ({ ...prev, wechatAccount: value })) + } + /> +
+ +
+

客服

+ ({ + label: c.name, + value: c.id, + }))} + value={filters.customerService} + onChange={(value) => + setFilters((prev) => ({ ...prev, customerService: value })) + } + /> +
+ +
+

流量池

+ ({ + label: p.name, + value: p.id, + }))} + value={filters.trafficPool} + onChange={(value) => + setFilters((prev) => ({ ...prev, trafficPool: value })) + } + /> +
+ +
+ +
+
+
+
+
+ ); +}; + +export default TrafficPoolList; diff --git a/nkebao/src/pages/orders/Orders.tsx b/nkebao/src/pages/orders/Orders.tsx deleted file mode 100644 index daecccd2..00000000 --- a/nkebao/src/pages/orders/Orders.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const Orders: React.FC = () => { - return ; -}; - -export default Orders; diff --git a/nkebao/src/pages/plans/PlanDetail.tsx b/nkebao/src/pages/plans/PlanDetail.tsx deleted file mode 100644 index 1d2e8b08..00000000 --- a/nkebao/src/pages/plans/PlanDetail.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const PlanDetail: React.FC = () => { - return ; -}; - -export default PlanDetail; diff --git a/nkebao/src/pages/plans/Plans.tsx b/nkebao/src/pages/plans/Plans.tsx deleted file mode 100644 index 1f0df647..00000000 --- a/nkebao/src/pages/plans/Plans.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const Plans: React.FC = () => { - return ( - - ); -}; - -export default Plans; diff --git a/nkebao/src/pages/profile/Profile.tsx b/nkebao/src/pages/profile/Profile.tsx deleted file mode 100644 index 87bc2d34..00000000 --- a/nkebao/src/pages/profile/Profile.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const Profile: React.FC = () => { - return ; -}; - -export default Profile; diff --git a/nkebao/src/pages/traffic-pool/detail/index.tsx b/nkebao/src/pages/traffic-pool/detail/index.tsx deleted file mode 100644 index 7389b966..00000000 --- a/nkebao/src/pages/traffic-pool/detail/index.tsx +++ /dev/null @@ -1,469 +0,0 @@ -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.tsx b/nkebao/src/pages/traffic-pool/list/index.tsx deleted file mode 100644 index f4d1da91..00000000 --- a/nkebao/src/pages/traffic-pool/list/index.tsx +++ /dev/null @@ -1,943 +0,0 @@ -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.added}
-
已添加
-
-
-
{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/index.tsx b/nkebao/src/router/module/index.tsx index 807dbf86..996b0aa3 100644 --- a/nkebao/src/router/module/index.tsx +++ b/nkebao/src/router/module/index.tsx @@ -1,8 +1,4 @@ import Home from "@/pages/home/index"; -import WechatAccounts from "@/pages/mine/wechat-accounts/list/index"; -import WechatAccountDetail from "@/pages/mine/wechat-accounts/detail/index"; -import Recharge from "@/pages/mine/recharge/index"; -import UserSetting from "@/pages/mine/userSet/index"; const routes = [ // 基础路由 @@ -11,27 +7,6 @@ const routes = [ element: , auth: true, // 需要登录 }, - // 微信号管理路由 - { - path: "/wechat-accounts", - element: , - auth: true, - }, - { - path: "/wechat-accounts/detail/:id", - element: , - auth: true, - }, - { - path: "/recharge", - element: , - auth: true, - }, - { - path: "/settings", - element: , - auth: true, - }, ]; export default routes; diff --git a/nkebao/src/router/module/mine.tsx b/nkebao/src/router/module/mine.tsx new file mode 100644 index 00000000..e6a8222b --- /dev/null +++ b/nkebao/src/router/module/mine.tsx @@ -0,0 +1,59 @@ +import Mine from "@/pages/mine/main/index"; +import Devices from "@/pages/mine/devices/index"; +import DeviceDetail from "@/pages/mine/devices/DeviceDetail"; +import TrafficPool from "@/pages/mine/traffic-pool/list/index"; +import TrafficPoolDetail from "@/pages/mine/traffic-pool/detail/index"; +import WechatAccounts from "@/pages/mine/wechat-accounts/list/index"; +import WechatAccountDetail from "@/pages/mine/wechat-accounts/detail/index"; +import Recharge from "@/pages/mine/recharge/index"; +import UserSetting from "@/pages/mine/userSet/index"; +const routes = [ + { + path: "/mine", + element: , + auth: true, + }, + { + path: "/devices", + element: , + auth: true, + }, + { + path: "/devices/:id", + element: , + auth: true, + }, + { + path: "/traffic-pool", + element: , + auth: true, + }, + { + path: "/traffic-pool/:id", + element: , + auth: true, + }, + // 微信号管理路由 + { + path: "/wechat-accounts", + element: , + auth: true, + }, + { + path: "/wechat-accounts/detail/:id", + element: , + auth: true, + }, + { + path: "/recharge", + element: , + auth: true, + }, + { + path: "/settings", + element: , + auth: true, + }, +]; + +export default routes; diff --git a/nkebao/src/router/module/other.tsx b/nkebao/src/router/module/other.tsx index 46c284fb..a43e47e5 100644 --- a/nkebao/src/router/module/other.tsx +++ b/nkebao/src/router/module/other.tsx @@ -1,30 +1,6 @@ -import Plans from "@/pages/plans/Plans"; -import PlanDetail from "@/pages/plans/PlanDetail"; -import Orders from "@/pages/orders/Orders"; -import ContactImport from "@/pages/contact-import/ContactImport"; import SelectionTest from "@/components/SelectionTest"; const otherRoutes = [ - { - path: "/plans", - element: , - auth: true, - }, - { - path: "/plans/:planId", - element: , - auth: true, - }, - { - path: "/orders", - element: , - auth: true, - }, - { - path: "/contact-import", - element: , - auth: true, - }, { path: "/selection-test", element: , diff --git a/nkebao/src/router/module/traffic-pool.tsx b/nkebao/src/router/module/traffic-pool.tsx deleted file mode 100644 index 99872ab7..00000000 --- a/nkebao/src/router/module/traffic-pool.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import TrafficPool from "@/pages/traffic-pool/list/index"; -import TrafficPoolDetail from "@/pages/traffic-pool/detail/index"; - -const trafficPoolRoutes = [ - { - path: "/traffic-pool", - element: , - auth: true, - }, - { - path: "/traffic-pool/:id", - element: , - auth: true, - }, -]; - -export default trafficPoolRoutes; diff --git a/nkebao/src/router/module/users.tsx b/nkebao/src/router/module/users.tsx deleted file mode 100644 index 72d8ea13..00000000 --- a/nkebao/src/router/module/users.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Mine from "@/pages/mine/main/index"; -import Devices from "@/pages/mine/devices/index"; -import DeviceDetail from "@/pages/mine/devices/DeviceDetail"; - -const routes = [ - { - path: "/mine", - element: , - auth: true, - }, - { - path: "/devices", - element: , - auth: true, - }, - { - path: "/devices/:id", - element: , - auth: true, - }, -]; - -export default routes;