From 2a40a4069761c36f5c6e4fa6754e46906c4a9eaf 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 14:17:07 +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?=E5=AD=98=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/pages/content/form/api.ts | 55 +- nkebao/src/pages/content/list/api.ts | 114 ++- .../src/pages/content/materials/form/api.ts | 69 +- .../pages/content/materials/form/index.tsx | 15 +- .../src/pages/content/materials/list/api.ts | 107 ++- .../pages/content/materials/list/index.tsx | 3 +- .../src/pages/mine/traffic-pool/detail/api.ts | 89 +-- .../pages/mine/traffic-pool/detail/data.ts | 0 .../traffic-pool/detail/index.module.scss | 354 ---------- .../pages/mine/traffic-pool/detail/index.tsx | 544 +-------------- .../src/pages/mine/traffic-pool/list/api.ts | 372 ---------- .../src/pages/mine/traffic-pool/list/data.ts | 0 .../mine/traffic-pool/list/index.module.scss | 365 ---------- .../pages/mine/traffic-pool/list/index.tsx | 659 +----------------- 14 files changed, 165 insertions(+), 2581 deletions(-) create mode 100644 nkebao/src/pages/mine/traffic-pool/detail/data.ts create mode 100644 nkebao/src/pages/mine/traffic-pool/list/data.ts diff --git a/nkebao/src/pages/content/form/api.ts b/nkebao/src/pages/content/form/api.ts index a0d86375c..4cce9f260 100644 --- a/nkebao/src/pages/content/form/api.ts +++ b/nkebao/src/pages/content/form/api.ts @@ -1,29 +1,26 @@ -import { get, post, put } from "@/api/request"; -import { - ApiResponse, - ContentLibrary, - CreateContentLibraryParams, - UpdateContentLibraryParams, -} from "./data"; - -// 获取内容库详情 -export async function getContentLibraryDetail( - id: string -): Promise> { - return get>(`/v1/content/library/${id}`); -} - -// 创建内容库 -export async function createContentLibrary( - params: CreateContentLibraryParams -): Promise> { - return post>("/v1/content/library", params); -} - -// 更新内容库 -export async function updateContentLibrary( - params: UpdateContentLibraryParams -): Promise> { - const { id, ...data } = params; - return put>(`/v1/content/library/${id}`, data); -} +import request from "@/api/request"; +import { + ContentLibrary, + CreateContentLibraryParams, + UpdateContentLibraryParams, +} from "./data"; + +// 获取内容库详情 +export function getContentLibraryDetail(id: string): Promise { + return request("/v1/content/library/detail", { id }, "GET"); +} + +// 创建内容库 +export function createContentLibrary( + params: CreateContentLibraryParams +): Promise { + return request("/v1/content/library/create", params, "POST"); +} + +// 更新内容库 +export function updateContentLibrary( + params: UpdateContentLibraryParams +): Promise { + const { id, ...data } = params; + return request(`/v1/content/library/update`, { id, ...data }, "POST"); +} diff --git a/nkebao/src/pages/content/list/api.ts b/nkebao/src/pages/content/list/api.ts index 2b9c36108..7bb985ee2 100644 --- a/nkebao/src/pages/content/list/api.ts +++ b/nkebao/src/pages/content/list/api.ts @@ -1,65 +1,49 @@ -import { get, post, put, del } from "@/api/request"; -import { - ApiResponse, - LibraryListResponse, - ContentLibrary, - CreateContentLibraryParams, - UpdateContentLibraryParams, -} from "./data"; - -// 获取内容库列表 -export async function getContentLibraryList(params: { - page?: number; - limit?: number; - keyword?: string; - sourceType?: number; -}): Promise> { - const queryParams = new URLSearchParams(); - - if (params.page) queryParams.append("page", params.page.toString()); - if (params.limit) queryParams.append("limit", params.limit.toString()); - if (params.keyword) queryParams.append("keyword", params.keyword); - if (params.sourceType) - queryParams.append("sourceType", params.sourceType.toString()); - - return get>( - `/v1/content/library/list?${queryParams.toString()}` - ); -} - -// 获取内容库详情 -export async function getContentLibraryDetail( - id: string -): Promise> { - return get>(`/v1/content/library/${id}`); -} - -// 创建内容库 -export async function createContentLibrary( - params: CreateContentLibraryParams -): Promise> { - return post>("/v1/content/library", params); -} - -// 更新内容库 -export async function updateContentLibrary( - params: UpdateContentLibraryParams -): Promise> { - const { id, ...data } = params; - return put>(`/v1/content/library/${id}`, data); -} - -// 删除内容库 -export async function deleteContentLibrary( - id: string -): Promise> { - return del>(`/v1/content/library/delete?id=${id}`); -} - -// 切换内容库状态 -export async function toggleContentLibraryStatus( - id: string, - status: number -): Promise> { - return put>(`/v1/content/library/${id}/status`, { status }); -} +import request from "@/api/request"; +import { + ContentLibrary, + CreateContentLibraryParams, + UpdateContentLibraryParams, +} from "./data"; + +// 获取内容库列表 +export function getContentLibraryList(params: { + page?: number; + limit?: number; + keyword?: string; + sourceType?: number; +}): Promise { + return request("/v1/content/library/list", params, "GET"); +} + +// 获取内容库详情 +export function getContentLibraryDetail(id: string): Promise { + return request("/v1/content/library/detail", { id }, "GET"); +} + +// 创建内容库 +export function createContentLibrary( + params: CreateContentLibraryParams +): Promise { + return request("/v1/content/library/create", params, "POST"); +} + +// 更新内容库 +export function updateContentLibrary( + params: UpdateContentLibraryParams +): Promise { + const { id, ...data } = params; + return request(`/v1/content/library/update`, { id, ...data }, "POST"); +} + +// 删除内容库 +export function deleteContentLibrary(id: string): Promise { + return request("/v1/content/library/delete", { id }, "DELETE"); +} + +// 切换内容库状态 +export function toggleContentLibraryStatus( + id: string, + status: number +): Promise { + return request("/v1/content/library/update-status", { id, status }, "POST"); +} diff --git a/nkebao/src/pages/content/materials/form/api.ts b/nkebao/src/pages/content/materials/form/api.ts index 198c9d2a8..8f792805d 100644 --- a/nkebao/src/pages/content/materials/form/api.ts +++ b/nkebao/src/pages/content/materials/form/api.ts @@ -1,37 +1,32 @@ -import { get, post, put } from "@/api/request"; -import { - ApiResponse, - ContentItem, - ContentLibrary, - CreateContentItemParams, - UpdateContentItemParams, -} from "./data"; - -// 获取素材详情 -export async function getContentItemDetail( - id: string -): Promise> { - return get>(`/v1/content/item/${id}`); -} - -// 创建素材 -export async function createContentItem( - params: CreateContentItemParams -): Promise> { - return post>("/v1/content/item", params); -} - -// 更新素材 -export async function updateContentItem( - params: UpdateContentItemParams -): Promise> { - const { id, ...data } = params; - return put>(`/v1/content/item/${id}`, data); -} - -// 获取内容库详情 -export async function getContentLibraryDetail( - id: string -): Promise> { - return get>(`/v1/content/library/${id}`); -} +import request from "@/api/request"; +import { + ContentItem, + ContentLibrary, + CreateContentItemParams, + UpdateContentItemParams, +} from "./data"; + +// 获取素材详情 +export function getContentItemDetail(id: string): Promise { + return request("/v1/content/item/detail", { id }, "GET"); +} + +// 创建素材 +export function createContentItem( + params: CreateContentItemParams +): Promise { + return request("/v1/content/item/create", params, "POST"); +} + +// 更新素材 +export function updateContentItem( + params: UpdateContentItemParams +): Promise { + const { id, ...data } = params; + return request(`/v1/content/item/update`, { id, ...data }, "POST"); +} + +// 获取内容库详情 +export function getContentLibraryDetail(id: string): Promise { + return request("/v1/content/library/detail", { id }, "GET"); +} diff --git a/nkebao/src/pages/content/materials/form/index.tsx b/nkebao/src/pages/content/materials/form/index.tsx index 32cd7676c..e42681350 100644 --- a/nkebao/src/pages/content/materials/form/index.tsx +++ b/nkebao/src/pages/content/materials/form/index.tsx @@ -1,16 +1,7 @@ import React, { useState, useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { - Button, - Toast, - SpinLoading, - Form, - Input, - Card, - Select, - Upload, -} from "antd-mobile"; -import { Input as AntdInput, TimePicker } from "antd"; +import { Button, Toast, SpinLoading, Form, Card } from "antd-mobile"; +import { Input, TimePicker, Select, Upload } from "antd"; import { ArrowLeftOutlined, SaveOutlined, @@ -33,7 +24,7 @@ import { ContentItem, ContentLibrary } from "./data"; import style from "./index.module.scss"; const { Option } = Select; -const { TextArea } = AntdInput; +const { TextArea } = Input; // 内容类型选项 const contentTypeOptions = [ diff --git a/nkebao/src/pages/content/materials/list/api.ts b/nkebao/src/pages/content/materials/list/api.ts index c329f060c..dd99af2bc 100644 --- a/nkebao/src/pages/content/materials/list/api.ts +++ b/nkebao/src/pages/content/materials/list/api.ts @@ -1,62 +1,45 @@ -import { get, post, put, del } from "@/api/request"; -import { - ApiResponse, - ItemListResponse, - ContentItem, - ContentLibrary, - GetContentItemListParams, - CreateContentItemParams, - UpdateContentItemParams, -} from "./data"; - -// 获取素材列表 -export async function getContentItemList( - params: GetContentItemListParams -): Promise> { - const queryParams = new URLSearchParams(); - - queryParams.append("libraryId", params.libraryId); - if (params.page) queryParams.append("page", params.page.toString()); - if (params.limit) queryParams.append("limit", params.limit.toString()); - if (params.keyword) queryParams.append("keyword", params.keyword); - - return get>( - `/v1/content/item/list?${queryParams.toString()}` - ); -} - -// 获取素材详情 -export async function getContentItemDetail( - id: string -): Promise> { - return get>(`/v1/content/item/${id}`); -} - -// 创建素材 -export async function createContentItem( - params: CreateContentItemParams -): Promise> { - return post>("/v1/content/item", params); -} - -// 更新素材 -export async function updateContentItem( - params: UpdateContentItemParams -): Promise> { - const { id, ...data } = params; - return put>(`/v1/content/item/${id}`, data); -} - -// 删除素材 -export async function deleteContentItem( - id: string -): Promise> { - return del>(`/v1/content/item/delete?id=${id}`); -} - -// 获取内容库详情 -export async function getContentLibraryDetail( - id: string -): Promise> { - return get>(`/v1/content/library/${id}`); -} +import request from "@/api/request"; +import { + ContentItem, + ContentLibrary, + GetContentItemListParams, + CreateContentItemParams, + UpdateContentItemParams, +} from "./data"; + +// 获取素材列表 +export function getContentItemList( + params: GetContentItemListParams +): Promise { + return request("/v1/content/item/list", params, "GET"); +} + +// 获取素材详情 +export function getContentItemDetail(id: string): Promise { + return request("/v1/content/item/detail", { id }, "GET"); +} + +// 创建素材 +export function createContentItem( + params: CreateContentItemParams +): Promise { + return request("/v1/content/item/create", params, "POST"); +} + +// 更新素材 +export function updateContentItem( + params: UpdateContentItemParams +): Promise { + const { id, ...data } = params; + return request(`/v1/content/item/update`, { id, ...data }, "POST"); +} + +// 删除素材 +export function deleteContentItem(id: string): Promise { + return request("/v1/content/item/delete", { id }, "DELETE"); +} + +// 获取内容库详情 +export function getContentLibraryDetail(id: string): Promise { + return request("/v1/content/library/detail", { id }, "GET"); +} diff --git a/nkebao/src/pages/content/materials/list/index.tsx b/nkebao/src/pages/content/materials/list/index.tsx index f4f43c3bc..93cdfae87 100644 --- a/nkebao/src/pages/content/materials/list/index.tsx +++ b/nkebao/src/pages/content/materials/list/index.tsx @@ -8,9 +8,8 @@ import { Card, Avatar, Tag, - Pagination, } from "antd-mobile"; -import { Input } from "antd"; +import { Pagination, Input } from "antd"; import { PlusOutlined, SearchOutlined, diff --git a/nkebao/src/pages/mine/traffic-pool/detail/api.ts b/nkebao/src/pages/mine/traffic-pool/detail/api.ts index 6675194e0..6b6377fe7 100644 --- a/nkebao/src/pages/mine/traffic-pool/detail/api.ts +++ b/nkebao/src/pages/mine/traffic-pool/detail/api.ts @@ -1,88 +1,5 @@ -import type { - Device, - WechatAccount, - CustomerService, - TrafficPool, - RFMScore, - UserTag, - UserInteraction, - TrafficUser, -} from "../list/api"; -import { - generateMockDevices, - generateMockWechatAccounts, - generateMockCustomerServices, - generateMockTrafficPools, - generateRFMScore, - generateUserTags, - generateMockInteractions, -} from "../list/api"; +import request from "@/api/request"; -// 获取单个流量池用户详情(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(), - }; +export function getTrafficPoolDetail(id: string): Promise { + return request("/v1/traffic/pool/detail", { id }, "GET"); } diff --git a/nkebao/src/pages/mine/traffic-pool/detail/data.ts b/nkebao/src/pages/mine/traffic-pool/detail/data.ts new file mode 100644 index 000000000..e69de29bb diff --git a/nkebao/src/pages/mine/traffic-pool/detail/index.module.scss b/nkebao/src/pages/mine/traffic-pool/detail/index.module.scss index 84fa5f364..e69de29bb 100644 --- a/nkebao/src/pages/mine/traffic-pool/detail/index.module.scss +++ b/nkebao/src/pages/mine/traffic-pool/detail/index.module.scss @@ -1,354 +0,0 @@ -.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/mine/traffic-pool/detail/index.tsx b/nkebao/src/pages/mine/traffic-pool/detail/index.tsx index e76eced98..bc908842c 100644 --- a/nkebao/src/pages/mine/traffic-pool/detail/index.tsx +++ b/nkebao/src/pages/mine/traffic-pool/detail/index.tsx @@ -1,541 +1,3 @@ -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; +export default function TrafficPoolDetail() { + return
TrafficPoolDetail
; +} diff --git a/nkebao/src/pages/mine/traffic-pool/list/api.ts b/nkebao/src/pages/mine/traffic-pool/list/api.ts index 12155a513..e69de29bb 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/api.ts +++ b/nkebao/src/pages/mine/traffic-pool/list/api.ts @@ -1,372 +0,0 @@ -// 类型定义 -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/mine/traffic-pool/list/data.ts b/nkebao/src/pages/mine/traffic-pool/list/data.ts new file mode 100644 index 000000000..e69de29bb diff --git a/nkebao/src/pages/mine/traffic-pool/list/index.module.scss b/nkebao/src/pages/mine/traffic-pool/list/index.module.scss index 6c5bf3c07..e69de29bb 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/index.module.scss +++ b/nkebao/src/pages/mine/traffic-pool/list/index.module.scss @@ -1,365 +0,0 @@ -.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/mine/traffic-pool/list/index.tsx b/nkebao/src/pages/mine/traffic-pool/list/index.tsx index 25de91e53..94006840b 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/index.tsx +++ b/nkebao/src/pages/mine/traffic-pool/list/index.tsx @@ -1,656 +1,3 @@ -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; +export default function TrafficPoolList() { + return
TrafficPoolList
; +}