From 029902de3d4a6eccca299f442b68acb6150e01ee 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 10:13:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B?= =?UTF-8?q?=20=E7=82=B9=E8=B5=9E=E8=AE=B0=E5=BD=95=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/workspace/auto-like/list/api.ts | 2 +- .../pages/workspace/auto-like/list/index.tsx | 2 +- .../src/pages/workspace/auto-like/new/api.ts | 2 +- .../pages/workspace/auto-like/new/index.tsx | 2 +- .../pages/workspace/auto-like/record/api.ts | 150 ++---- .../pages/workspace/auto-like/record/data.ts | 258 ++++------ .../workspace/auto-like/record/index.tsx | 474 +++++++++--------- .../auto-like/record/record.module.scss | 473 +++++++---------- 8 files changed, 591 insertions(+), 772 deletions(-) diff --git a/nkebao/src/pages/workspace/auto-like/list/api.ts b/nkebao/src/pages/workspace/auto-like/list/api.ts index 34d4424a..fae08ec4 100644 --- a/nkebao/src/pages/workspace/auto-like/list/api.ts +++ b/nkebao/src/pages/workspace/auto-like/list/api.ts @@ -5,7 +5,7 @@ import { UpdateLikeTaskData, LikeRecord, PaginatedResponse, -} from "@/pages/workspace/auto-like/record/api"; +} from "@/pages/workspace/auto-like/record/data"; // 获取自动点赞任务列表 export function fetchAutoLikeTasks( diff --git a/nkebao/src/pages/workspace/auto-like/list/index.tsx b/nkebao/src/pages/workspace/auto-like/list/index.tsx index 9851eeaf..81f46d1d 100644 --- a/nkebao/src/pages/workspace/auto-like/list/index.tsx +++ b/nkebao/src/pages/workspace/auto-like/list/index.tsx @@ -23,7 +23,7 @@ import { toggleAutoLikeTask, copyAutoLikeTask, } from "./api"; -import { LikeTask } from "@/pages/workspace/auto-like/record/api"; +import { LikeTask } from "@/pages/workspace/auto-like/record/data"; import style from "./index.module.scss"; // 卡片菜单组件 diff --git a/nkebao/src/pages/workspace/auto-like/new/api.ts b/nkebao/src/pages/workspace/auto-like/new/api.ts index 703d7bb6..8a684aa3 100644 --- a/nkebao/src/pages/workspace/auto-like/new/api.ts +++ b/nkebao/src/pages/workspace/auto-like/new/api.ts @@ -3,7 +3,7 @@ import { CreateLikeTaskData, UpdateLikeTaskData, LikeTask, -} from "@/pages/workspace/auto-like/record/api"; +} from "@/pages/workspace/auto-like/record/data"; // 获取自动点赞任务详情 export function fetchAutoLikeTaskDetail(id: string): Promise { diff --git a/nkebao/src/pages/workspace/auto-like/new/index.tsx b/nkebao/src/pages/workspace/auto-like/new/index.tsx index 8d04bc0a..5ad689ea 100644 --- a/nkebao/src/pages/workspace/auto-like/new/index.tsx +++ b/nkebao/src/pages/workspace/auto-like/new/index.tsx @@ -15,7 +15,7 @@ import { import { CreateLikeTaskData, ContentType, -} from "@/pages/workspace/auto-like/record/api"; +} from "@/pages/workspace/auto-like/record/data"; import style from "./new.module.scss"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; diff --git a/nkebao/src/pages/workspace/auto-like/record/api.ts b/nkebao/src/pages/workspace/auto-like/record/api.ts index bace06ec..a0ffdfa5 100644 --- a/nkebao/src/pages/workspace/auto-like/record/api.ts +++ b/nkebao/src/pages/workspace/auto-like/record/api.ts @@ -1,119 +1,63 @@ -// 自动点赞任务状态 -export type LikeTaskStatus = 1 | 2; // 1: 开启, 2: 关闭 +import request from "@/api/request"; +import { + LikeTask, + CreateLikeTaskData, + UpdateLikeTaskData, + LikeRecord, + PaginatedResponse, +} from "@/pages/workspace/auto-like/record/data"; -// 内容类型 -export type ContentType = "text" | "image" | "video" | "link"; - -// 设备信息 -export interface Device { - id: string; - name: string; - status: "online" | "offline"; - lastActive: string; +// 获取自动点赞任务列表 +export function fetchAutoLikeTasks( + params = { type: 1, page: 1, limit: 100 } +): Promise { + return request("/v1/workbench/list", params, "GET"); } -// 好友信息 -export interface Friend { - id: string; - nickname: string; - wechatId: string; - avatar: string; - tags: string[]; - region: string; - source: string; +// 获取单个任务详情 +export function fetchAutoLikeTaskDetail(id: string): Promise { + return request("/v1/workbench/detail", { id }, "GET"); } -// 点赞记录 -export interface LikeRecord { - id: string; - workbenchId: string; - momentsId: string; - snsId: string; - wechatAccountId: string; - wechatFriendId: string; - likeTime: string; - content: string; - resUrls: string[]; - momentTime: string; - userName: string; - operatorName: string; - operatorAvatar: string; - friendName: string; - friendAvatar: string; +// 创建自动点赞任务 +export function createAutoLikeTask(data: CreateLikeTaskData): Promise { + return request("/v1/workbench/create", { ...data, type: 1 }, "POST"); } -// 自动点赞任务 -export interface LikeTask { - id: string; - name: string; - status: LikeTaskStatus; - deviceCount: number; - targetGroup: string; - likeCount: number; - lastLikeTime: string; - createTime: string; - creator: string; - likeInterval: number; - maxLikesPerDay: number; - timeRange: { start: string; end: string }; - contentTypes: ContentType[]; - targetTags: string[]; - devices: string[]; - friends: string[]; - friendMaxLikes: number; - friendTags: string; - enableFriendTags: boolean; - todayLikeCount: number; - totalLikeCount: number; - updateTime: string; +// 更新自动点赞任务 +export function updateAutoLikeTask(data: UpdateLikeTaskData): Promise { + return request("/v1/workbench/update", { ...data, type: 1 }, "POST"); } -// 创建任务数据 -export interface CreateLikeTaskData { - name: string; - interval: number; - maxLikes: number; - startTime: string; - endTime: string; - contentTypes: ContentType[]; - devices: string[]; - friends?: string[]; - friendMaxLikes: number; - friendTags?: string; - enableFriendTags: boolean; - targetTags: string[]; +// 删除自动点赞任务 +export function deleteAutoLikeTask(id: string): Promise { + return request("/v1/workbench/delete", { id }, "DELETE"); } -// 更新任务数据 -export interface UpdateLikeTaskData extends CreateLikeTaskData { - id: string; +// 切换任务状态 +export function toggleAutoLikeTask(id: string, status: string): Promise { + return request("/v1/workbench/update-status", { id, status }, "POST"); } -// 任务配置 -export interface TaskConfig { - interval: number; - maxLikes: number; - startTime: string; - endTime: string; - contentTypes: ContentType[]; - devices: string[]; - friends: string[]; - friendMaxLikes: number; - friendTags: string; - enableFriendTags: boolean; +// 复制自动点赞任务 +export function copyAutoLikeTask(id: string): Promise { + return request("/v1/workbench/copy", { id }, "POST"); } -// API响应类型 -export interface ApiResponse { - code: number; - msg: string; - data: T; -} - -// 分页响应类型 -export interface PaginatedResponse { - list: T[]; - total: number; - page: number; - limit: number; +// 获取点赞记录 +export function fetchLikeRecords( + workbenchId: string, + page: number = 1, + limit: number = 20, + keyword?: string +): Promise> { + const params: any = { + workbenchId, + page: page.toString(), + limit: limit.toString(), + }; + if (keyword) { + params.keyword = keyword; + } + return request("/v1/workbench/like-records", params, "GET"); } diff --git a/nkebao/src/pages/workspace/auto-like/record/data.ts b/nkebao/src/pages/workspace/auto-like/record/data.ts index 122a093d..bace06ec 100644 --- a/nkebao/src/pages/workspace/auto-like/record/data.ts +++ b/nkebao/src/pages/workspace/auto-like/record/data.ts @@ -1,173 +1,119 @@ -import { request } from "../../../../api/request"; -import { - LikeTask, - CreateLikeTaskData, - UpdateLikeTaskData, - LikeRecord, - ApiResponse, - PaginatedResponse, -} from "@/pages/workspace/auto-like/record/api"; +// 自动点赞任务状态 +export type LikeTaskStatus = 1 | 2; // 1: 开启, 2: 关闭 -// 获取自动点赞任务列表 -export async function fetchAutoLikeTasks(): Promise { - try { - const res = await request>>({ - url: "/v1/workbench/list", - method: "GET", - params: { - type: 1, - page: 1, - limit: 100, - }, - }); +// 内容类型 +export type ContentType = "text" | "image" | "video" | "link"; - if (res.code === 200 && res.data) { - return res.data.list || []; - } - return []; - } catch (error) { - console.error("获取自动点赞任务失败:", error); - return []; - } +// 设备信息 +export interface Device { + id: string; + name: string; + status: "online" | "offline"; + lastActive: string; } -// 获取单个任务详情 -export async function fetchAutoLikeTaskDetail( - id: string -): Promise { - try { - console.log(`Fetching task detail for id: ${id}`); - const res = await request({ - url: "/v1/workbench/detail", - method: "GET", - params: { id }, - }); - console.log("Task detail API response:", res); - - if (res.code === 200) { - if (res.data) { - if (typeof res.data === "object") { - return res.data; - } else { - console.error( - "Task detail API response data is not an object:", - res.data - ); - return null; - } - } else { - console.error("Task detail API response missing data field:", res); - return null; - } - } - - console.error("Task detail API error:", res.msg || "Unknown error"); - return null; - } catch (error) { - console.error("获取任务详情失败:", error); - return null; - } +// 好友信息 +export interface Friend { + id: string; + nickname: string; + wechatId: string; + avatar: string; + tags: string[]; + region: string; + source: string; } -// 创建自动点赞任务 -export async function createAutoLikeTask( - data: CreateLikeTaskData -): Promise { - return request({ - url: "/v1/workbench/create", - method: "POST", - data: { - ...data, - type: 1, // 自动点赞类型 - }, - }); +// 点赞记录 +export interface LikeRecord { + id: string; + workbenchId: string; + momentsId: string; + snsId: string; + wechatAccountId: string; + wechatFriendId: string; + likeTime: string; + content: string; + resUrls: string[]; + momentTime: string; + userName: string; + operatorName: string; + operatorAvatar: string; + friendName: string; + friendAvatar: string; } -// 更新自动点赞任务 -export async function updateAutoLikeTask( - data: UpdateLikeTaskData -): Promise { - return request({ - url: "/v1/workbench/update", - method: "POST", - data: { - ...data, - type: 1, // 自动点赞类型 - }, - }); +// 自动点赞任务 +export interface LikeTask { + id: string; + name: string; + status: LikeTaskStatus; + deviceCount: number; + targetGroup: string; + likeCount: number; + lastLikeTime: string; + createTime: string; + creator: string; + likeInterval: number; + maxLikesPerDay: number; + timeRange: { start: string; end: string }; + contentTypes: ContentType[]; + targetTags: string[]; + devices: string[]; + friends: string[]; + friendMaxLikes: number; + friendTags: string; + enableFriendTags: boolean; + todayLikeCount: number; + totalLikeCount: number; + updateTime: string; } -// 删除自动点赞任务 -export async function deleteAutoLikeTask(id: string): Promise { - return request({ - url: "/v1/workbench/delete", - method: "DELETE", - params: { id }, - }); +// 创建任务数据 +export interface CreateLikeTaskData { + name: string; + interval: number; + maxLikes: number; + startTime: string; + endTime: string; + contentTypes: ContentType[]; + devices: string[]; + friends?: string[]; + friendMaxLikes: number; + friendTags?: string; + enableFriendTags: boolean; + targetTags: string[]; } -// 切换任务状态 -export async function toggleAutoLikeTask( - id: string, - status: string -): Promise { - return request({ - url: "/v1/workbench/update-status", - method: "POST", - data: { id, status }, - }); +// 更新任务数据 +export interface UpdateLikeTaskData extends CreateLikeTaskData { + id: string; } -// 复制自动点赞任务 -export async function copyAutoLikeTask(id: string): Promise { - return request({ - url: "/v1/workbench/copy", - method: "POST", - data: { id }, - }); +// 任务配置 +export interface TaskConfig { + interval: number; + maxLikes: number; + startTime: string; + endTime: string; + contentTypes: ContentType[]; + devices: string[]; + friends: string[]; + friendMaxLikes: number; + friendTags: string; + enableFriendTags: boolean; } -// 获取点赞记录 -export async function fetchLikeRecords( - workbenchId: string, - page: number = 1, - limit: number = 20, - keyword?: string -): Promise> { - try { - const params: any = { - workbenchId, - page: page.toString(), - limit: limit.toString(), - }; - - if (keyword) { - params.keyword = keyword; - } - - const res = await request>>({ - url: "/v1/workbench/records", - method: "GET", - params, - }); - - if (res.code === 200 && res.data) { - return res.data; - } - - return { - list: [], - total: 0, - page: 1, - limit: 20, - }; - } catch (error) { - console.error("获取点赞记录失败:", error); - return { - list: [], - total: 0, - page: 1, - limit: 20, - }; - } +// API响应类型 +export interface ApiResponse { + code: number; + msg: string; + data: T; +} + +// 分页响应类型 +export interface PaginatedResponse { + list: T[]; + total: number; + page: number; + limit: number; } diff --git a/nkebao/src/pages/workspace/auto-like/record/index.tsx b/nkebao/src/pages/workspace/auto-like/record/index.tsx index 5ccc5ce4..9bd51eda 100644 --- a/nkebao/src/pages/workspace/auto-like/record/index.tsx +++ b/nkebao/src/pages/workspace/auto-like/record/index.tsx @@ -1,20 +1,27 @@ import React, { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; -import { NavBar, Button, Toast, SpinLoading, Card, Avatar } from "antd-mobile"; -import { Input } from "antd"; -import InfiniteList from "@/components/InfiniteList/InfiniteList"; import { - SearchOutlined, - ReloadOutlined, + Button, + Input, + Card, + Badge, + Avatar, + Skeleton, + message, + Spin, + Divider, + Pagination, +} from "antd"; +import { LikeOutlined, + ReloadOutlined, + SearchOutlined, UserOutlined, } from "@ant-design/icons"; - +import styles from "./record.module.scss"; +import NavCommon from "@/components/NavCommon"; +import { fetchLikeRecords } from "./api"; import Layout from "@/components/Layout/Layout"; -import MeauMobile from "@/components/MeauMobile/MeauMoible"; -import { fetchLikeRecords, fetchAutoLikeTaskDetail } from "./data"; -import { LikeRecord, LikeTask } from "./api"; -import style from "./record.module.scss"; // 格式化日期 const formatDate = (dateString: string) => { @@ -32,264 +39,271 @@ const formatDate = (dateString: string) => { } }; -const AutoLikeDetail: React.FC = () => { +export default function AutoLikeRecord() { const { id } = useParams<{ id: string }>(); - const [records, setRecords] = useState([]); - const [taskDetail, setTaskDetail] = useState(null); + const [records, setRecords] = useState([]); const [recordsLoading, setRecordsLoading] = useState(false); - const [taskLoading, setTaskLoading] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [total, setTotal] = useState(0); - const [hasMore, setHasMore] = useState(true); const pageSize = 10; - // 获取任务详情 - const fetchTaskDetail = async () => { - if (!id) return; - setTaskLoading(true); - try { - const detail = await fetchAutoLikeTaskDetail(id); - setTaskDetail(detail); - } catch (error) { - Toast.show({ - content: "获取任务详情失败", - position: "top", - }); - } finally { - setTaskLoading(false); - } - }; - - // 获取点赞记录 - const fetchRecords = async ( - page: number = 1, - isLoadMore: boolean = false - ) => { - if (!id) return; - - if (!isLoadMore) { - setRecordsLoading(true); - } - - try { - const response = await fetchLikeRecords(id, page, pageSize, searchTerm); - const newRecords = response.list || []; - - if (isLoadMore) { - setRecords((prev) => [...prev, ...newRecords]); - } else { - setRecords(newRecords); - } - - setTotal(response.total || 0); - setCurrentPage(page); - setHasMore(newRecords.length === pageSize); - } catch (error) { - Toast.show({ - content: "获取点赞记录失败", - position: "top", - }); - } finally { - setRecordsLoading(false); - } - }; - useEffect(() => { - fetchTaskDetail(); - fetchRecords(1, false); + if (!id) return; + setRecordsLoading(true); + fetchLikeRecords(id, 1, pageSize) + .then((response: any) => { + setRecords(response.list || []); + setTotal(response.total || 0); + setCurrentPage(1); + }) + .catch(() => { + message.error("获取点赞记录失败,请稍后重试"); + }) + .finally(() => setRecordsLoading(false)); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); const handleSearch = () => { setCurrentPage(1); - fetchRecords(1, false); + fetchLikeRecords(id!, 1, pageSize, searchTerm) + .then((response: any) => { + setRecords(response.list || []); + setTotal(response.total || 0); + setCurrentPage(1); + }) + .catch(() => { + message.error("获取点赞记录失败,请稍后重试"); + }); }; const handleRefresh = () => { - fetchRecords(currentPage, false); + fetchLikeRecords(id!, currentPage, pageSize, searchTerm) + .then((response: any) => { + setRecords(response.list || []); + setTotal(response.total || 0); + }) + .catch(() => { + message.error("获取点赞记录失败,请稍后重试"); + }); }; - const handleLoadMore = async () => { - if (hasMore && !recordsLoading) { - await fetchRecords(currentPage + 1, true); - } + const handlePageChange = (newPage: number) => { + fetchLikeRecords(id!, newPage, pageSize, searchTerm) + .then((response: any) => { + setRecords(response.list || []); + setTotal(response.total || 0); + setCurrentPage(newPage); + }) + .catch(() => { + message.error("获取点赞记录失败,请稍后重试"); + }); }; - const renderRecordItem = (record: LikeRecord) => ( - -
-
- } - /> -
-
- {record.friendName} -
-
内容发布者
-
-
-
- {formatDate(record.momentTime || record.likeTime)} -
-
- -
- {record.content && ( -

{record.content}

- )} - - {Array.isArray(record.resUrls) && record.resUrls.length > 0 && ( -
- {record.resUrls.slice(0, 9).map((image: string, idx: number) => ( -
- {`内容图片 -
- ))} -
- )} -
- -
- } - /> -
- - {record.operatorName} - - 点赞了这条内容 -
-
-
- ); - return ( window.history.back()} - left={ -
- 点赞记录 -
- } - /> - } - footer={} - > -
- {/* 任务信息卡片 */} - {taskDetail && ( -
-
-

{taskDetail.name}

- - {Number(taskDetail.status) === 1 ? "进行中" : "已暂停"} - -
-
-
- - 今日点赞: - - {taskDetail.todayLikeCount || 0} - -
-
- - 总点赞数: - - {taskDetail.totalLikeCount || 0} - -
-
-
- )} - - {/* 搜索区域 */} -
-
-
- + <> + +
+
} placeholder="搜索好友昵称或内容" - className={style["search-input"]} + className={styles.headerSearchInput} value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} onPressEnter={handleSearch} + allowClear />
- + loading={recordsLoading} + type="default" + shape="circle" + />
-
- - {/* 记录列表 */} -
- {recordsLoading && currentPage === 1 ? ( -
- -
加载中...
+ + } + footer={ + <> +
+ + `第 ${range[0]}-${range[1]} 条,共 ${total} 条` + } + size="default" + className={styles.pagination} + /> +
+ + } + > +
+
+ {recordsLoading ? ( +
+ {Array.from({ length: 3 }).map((_, index) => ( +
+
+ +
+ + +
+
+ +
+ + +
+ + +
+
+
+ ))}
) : records.length === 0 ? ( -
- -
暂无点赞记录
+
+ +

暂无点赞记录

) : ( - + <> + {records.map((record) => ( +
+
+
+ } + size={40} + className={styles.avatarImg} + /> +
+
+ {record.friendName} +
+
内容发布者
+
+
+ +
+ +
+ {record.content && ( +

{record.content}

+ )} + {Array.isArray(record.resUrls) && + record.resUrls.length > 0 && ( +
+ {record.resUrls + .slice(0, 9) + .map((image: string, idx: number) => ( +
+ {`内容图片 +
+ ))} +
+ )} +
+
+ } + size={32} + className={styles.operatorAvatar} + /> +
+ + {record.operatorName} + + + + 已赞 + +
+
+
+ ))} + )}
); -}; - -export default AutoLikeDetail; +} diff --git a/nkebao/src/pages/workspace/auto-like/record/record.module.scss b/nkebao/src/pages/workspace/auto-like/record/record.module.scss index 9bc931b6..ae062d02 100644 --- a/nkebao/src/pages/workspace/auto-like/record/record.module.scss +++ b/nkebao/src/pages/workspace/auto-like/record/record.module.scss @@ -1,351 +1,266 @@ -.detail-page { - background: #f5f5f5; - min-height: 100vh; - padding-bottom: 80px; -} - -.task-info-card { - background: white; - margin: 16px; - border-radius: 8px; - padding: 16px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.task-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 16px; -} - -.task-name { - font-size: 16px; - font-weight: 600; - color: #333; - margin: 0; -} - -.task-status { - padding: 2px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - - &.active { - background: #f6ffed; - color: #52c41a; - border: 1px solid #b7eb8f; - } - - &.inactive { - background: #f5f5f5; - color: #666; - border: 1px solid #d9d9d9; - } -} - -.task-stats { - display: flex; - justify-content: space-between; - gap: 16px; -} - -.stat-item { - display: flex; - align-items: center; - gap: 6px; - font-size: 14px; - color: #666; -} - -.stat-icon { - font-size: 16px; - color: #1890ff; -} - -.stat-label { - font-weight: 500; -} - -.stat-value { - color: #333; - font-weight: 600; -} - -.search-section { - padding: 0 16px 16px; -} - -.search-wrapper { +// 搜索栏 +.headerSearchBar { display: flex; align-items: center; gap: 8px; - background: white; - border-radius: 8px; - padding: 12px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 16px; } - -.search-input-wrapper { +.headerSearchInputWrap { position: relative; flex: 1; } - -.search-input { - width: 100%; - height: 36px; - padding: 0 12px 0 32px; - border: 1px solid #d9d9d9; - border-radius: 6px; - font-size: 14px; - - &:focus { - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - outline: none; - } -} - -.search-icon { +.headerSearchIcon { position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - color: #999; - font-size: 14px; + left: 12px; + top: 10px; + width: 16px; + height: 16px; + color: #a3a3a3; +} +.headerSearchInput { + padding-left: 32px !important; +} +.spin { + animation: spin 1s linear infinite; +} +@keyframes spin { + 100% { transform: rotate(360deg); } } -.search-btn { - height: 36px; - padding: 0 12px; - border-radius: 6px; - font-size: 14px; - white-space: nowrap; -} - -.refresh-btn { - height: 36px; - width: 36px; - padding: 0; - border-radius: 6px; +// 分页 +.footerPagination { display: flex; - align-items: center; justify-content: center; - border: 1px solid #d9d9d9; - background: white; - cursor: pointer; - transition: all 0.2s; - - &:hover { + align-items: center; + padding: 12px 0; + background: #fff; +} +.pagination { + :global(.ant-pagination-item) { + border-radius: 6px; + } + :global(.ant-pagination-item-active) { + background: #1890ff; border-color: #1890ff; - color: #1890ff; } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; + :global(.ant-pagination-prev), + :global(.ant-pagination-next) { + border-radius: 6px; + } + :global(.ant-pagination-jump-prev), + :global(.ant-pagination-jump-next) { + border-radius: 6px; } } -.records-section { - padding: 0 16px; +// 背景和内容 +.bgWrap { + background: #f7f7fa; + min-height: 100vh; + padding-bottom: 80px; } - -.records-list { +.contentWrap { + padding: 12px; display: flex; flex-direction: column; + gap: 16px; +} + +// 骨架屏 +.skeletonWrap { + display: flex; + flex-direction: column; + gap: 16px; +} +.skeletonCard { + padding: 0px; +} +.skeletonCardHeader { + display: flex; + align-items: center; gap: 12px; + margin-bottom: 12px; } - -.record-card { - background: white; +.skeletonAvatar { + width: 40px; + height: 40px; + border-radius: 50%; +} +.skeletonNameWrap { + display: flex; + flex-direction: column; + gap: 8px; +} +.skeletonName { + width: 96px; + height: 16px; +} +.skeletonSub { + width: 64px; + height: 12px; +} +.skeletonSep { + margin: 12px 0; +} +.skeletonContentWrap { + display: flex; + flex-direction: column; + gap: 8px; +} +.skeletonContent1 { + width: 100%; + height: 16px; +} +.skeletonContent2 { + width: 75%; + height: 16px; +} +.skeletonImgWrap { + display: flex; + gap: 8px; + margin-top: 12px; +} +.skeletonImg { + width: 80px; + height: 80px; border-radius: 8px; - padding: 16px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } -.record-header { +// 空状态 +.emptyWrap { + text-align: center; + padding: 48px 0; +} +.emptyIcon { + width: 48px; + height: 48px; + color: #e5e7eb; + margin: 0 auto 12px auto; +} +.emptyText { + color: #888; + font-size: 16px; +} + +// 记录卡片 +.recordCard { + background: #fff; + border-radius: 16px; + box-shadow: 0 2px 8px rgba(0,0,0,0.04); + padding: 16px; +} +.recordCardHeader { display: flex; align-items: flex-start; justify-content: space-between; - margin-bottom: 12px; } - -.user-info { +.recordCardHeaderLeft { display: flex; align-items: center; gap: 12px; max-width: 65%; } - -.user-avatar { +.avatarImg { width: 40px; height: 40px; border-radius: 50%; - flex-shrink: 0; } - -.user-details { +.friendInfo { min-width: 0; } - -.user-name { - font-size: 14px; - font-weight: 600; - color: #333; +.friendName { + font-weight: 500; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; } - -.user-role { - font-size: 12px; - color: #666; - margin-top: 2px; +.friendSub { + font-size: 13px; + color: #888; } - -.record-time { - font-size: 12px; - color: #666; - background: #f8f9fa; - padding: 4px 8px; - border-radius: 4px; +.timeBadge { + background: #e8f0fe; white-space: nowrap; flex-shrink: 0; } - -.record-content { +.cardSep { + margin: 12px 0; +} +.cardContent { margin-bottom: 12px; } - -.content-text { - font-size: 14px; - color: #333; - line-height: 1.5; +.contentText { + color: #444; margin-bottom: 12px; white-space: pre-line; } - -.content-images { +.imgGrid { display: grid; - gap: 4px; - - &.single { - grid-template-columns: 1fr; - } - - &.double { - grid-template-columns: 1fr 1fr; - } - - &.multiple { - grid-template-columns: repeat(3, 1fr); - } + gap: 8px; } - -.image-item { - aspect-ratio: 1; - border-radius: 6px; +.grid1 { + grid-template-columns: 1fr; +} +.grid2 { + grid-template-columns: 1fr 1fr; +} +.grid3 { + grid-template-columns: 1fr 1fr 1fr; +} +.grid6 { + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr; +} +.grid9 { + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr; +} +.imgItem { + position: relative; + aspect-ratio: 1/1; + border-radius: 8px; overflow: hidden; } - -.content-image { +.img { width: 100%; height: 100%; object-fit: cover; } -.like-info { +// 操作人 +.operatorWrap { display: flex; align-items: center; - gap: 8px; - padding: 8px 12px; - background: #f8f9fa; - border-radius: 6px; + margin-top: 16px; + padding: 8px; + background: #f3f4f6; + border-radius: 8px; } - -.operator-avatar { - width: 32px; - height: 32px; - border-radius: 50%; +.operatorAvatar { + width: 32px !important; + height: 32px !important; + margin-right: 8px; flex-shrink: 0; } - -.like-text { +.operatorInfo { font-size: 14px; - color: #666; - min-width: 0; + position: relative; + flex: 1; + position: relative; } - -.operator-name { - font-weight: 600; - color: #333; +.operatorName { + font-weight: 500; + max-width: 100%; + display: inline-block; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; - display: inline-block; - max-width: 100%; } - -.like-action { - margin-left: 4px; +.operatorAction { + color: #888; + margin-left: 8px; + font-size: 12px; + position: absolute; + right: 0; + top: 2px; } - -.loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 60vh; - gap: 16px; -} - -.loading-text { - color: #666; - font-size: 14px; -} - -.empty-state { - text-align: center; - padding: 60px 20px; - color: #666; -} - -.empty-icon { - font-size: 48px; - color: #d9d9d9; - margin-bottom: 16px; -} - -.empty-text { - font-size: 16px; - color: #999; -} - -// 移动端适配 -@media (max-width: 768px) { - .task-stats { - flex-direction: column; - gap: 12px; - } - - .search-wrapper { - flex-direction: column; - gap: 12px; - } - - .search-btn { - width: 100%; - } - - .user-info { - max-width: 60%; - } - - .content-images { - &.multiple { - grid-template-columns: repeat(2, 1fr); - } - } -} \ No newline at end of file From 40e7593f99862a2d909148f1a090d24eb5d5fcd3 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:10:25 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B?= =?UTF-8?q?=20=E6=B5=81=E9=87=8F=E6=B1=A0=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../traffic-pool/detail/index.module.scss | 354 +++++++ .../src/pages/traffic-pool/detail/index.tsx | 469 +++++++++ .../pages/traffic-pool/list/index.module.scss | 365 +++++++ nkebao/src/pages/traffic-pool/list/index.tsx | 943 ++++++++++++++++++ nkebao/src/router/module/traffic-pool.tsx | 4 +- 5 files changed, 2133 insertions(+), 2 deletions(-) create mode 100644 nkebao/src/pages/traffic-pool/detail/index.module.scss create mode 100644 nkebao/src/pages/traffic-pool/detail/index.tsx create mode 100644 nkebao/src/pages/traffic-pool/list/index.module.scss create mode 100644 nkebao/src/pages/traffic-pool/list/index.tsx diff --git a/nkebao/src/pages/traffic-pool/detail/index.module.scss b/nkebao/src/pages/traffic-pool/detail/index.module.scss new file mode 100644 index 00000000..84fa5f36 --- /dev/null +++ b/nkebao/src/pages/traffic-pool/detail/index.module.scss @@ -0,0 +1,354 @@ +.container { + padding: 16px; + background: #f5f5f5; + min-height: 100vh; +} + +.notFound { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; +} + +.notFoundText { + color: #999; + font-size: 16px; +} + +.userCard { + margin-bottom: 16px; + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.userHeader { + display: flex; + align-items: flex-start; + gap: 16px; + margin-bottom: 12px; +} + +.userAvatar { + width: 64px; + height: 64px; + border-radius: 50%; + flex-shrink: 0; +} + +.userInfo { + flex: 1; + min-width: 0; +} + +.userName { + font-size: 18px; + font-weight: 600; + color: #333; + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; + line-height: 1.2; +} + +.starIcon { + color: #ff4d4f; + font-size: 16px; +} + +.userWechatId { + font-size: 14px; + color: #1677ff; + font-weight: 500; + margin-bottom: 8px; +} + +.userTags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.rfmTags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.tabs { + background: #fff; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.tabContent { + padding: 16px; +} + +.cardTitle { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 12px; +} + +.infoCard { + margin-bottom: 16px; + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.infoItem { + padding: 8px 0; +} + +.infoLabel { + font-size: 12px; + color: #666; + margin-bottom: 4px; +} + +.infoValue { + font-size: 14px; + color: #333; + font-weight: 500; +} + +.rfmCard { + margin-bottom: 16px; + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.rfmGrid { + display: flex; + justify-content: space-around; + text-align: center; +} + +.rfmItem { + flex: 1; +} + +.rfmValue { + font-size: 24px; + font-weight: bold; + line-height: 1; + margin-bottom: 4px; +} + +.rfmLabel { + font-size: 12px; + color: #666; +} + +.rfmItem:nth-child(1) .rfmValue { + color: #1677ff; +} + +.rfmItem:nth-child(2) .rfmValue { + color: #52c41a; +} + +.rfmItem:nth-child(3) .rfmValue { + color: #722ed1; +} + +.poolButtons { + display: flex; + gap: 12px; + margin-bottom: 16px; +} + +.statsCard { + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.statItem { + text-align: center; + padding: 8px 0; +} + +.statValue { + font-size: 18px; + font-weight: bold; + line-height: 1; + margin-bottom: 4px; +} + +.statLabel { + font-size: 12px; + color: #666; +} + +.statItem:nth-child(1) .statValue { + color: #52c41a; +} + +.statItem:nth-child(2) .statValue { + color: #1677ff; +} + +.statItem:nth-child(3) .statValue { + color: #faad14; +} + +.statItem:nth-child(4) .statValue { + color: #ff4d4f; +} + +.interactionCard { + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.interactionList { + margin: 0; + padding: 0; +} + +.interactionItem { + padding: 12px 0; + border-bottom: 1px solid #f0f0f0; + + &:last-child { + border-bottom: none; + } +} + +.interactionIcon { + font-size: 20px; + color: #1677ff; + margin-right: 12px; +} + +.interactionContent { + flex: 1; + min-width: 0; +} + +.interactionTitle { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 4px; +} + +.interactionDesc { + font-size: 12px; + color: #666; + margin-bottom: 4px; + line-height: 1.4; +} + +.interactionValue { + color: #52c41a; + font-weight: bold; + margin-left: 4px; +} + +.interactionTime { + font-size: 11px; + color: #999; +} + +.emptyState { + text-align: center; + color: #999; + padding: 32px 0; + font-size: 14px; +} + +.tagsCard { + margin-bottom: 16px; + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.tagsList { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; +} + +.valueTags { + border-top: 1px solid #f0f0f0; + padding-top: 16px; +} + +.valueTitle { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 12px; +} + +.valueTagItem { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.rfmScore { + font-size: 12px; + color: #666; +} + +.valueLabel { + font-size: 12px; + color: #666; +} + +.addTagButton { + margin-top: 16px; +} + +// 响应式设计 +@media (max-width: 375px) { + .container { + padding: 12px; + } + + .userHeader { + flex-direction: column; + align-items: center; + text-align: center; + } + + .userAvatar { + width: 80px; + height: 80px; + } + + .rfmGrid { + flex-direction: column; + gap: 16px; + } + + .poolButtons { + flex-direction: column; + } + + .statsCard { + .adm-grid { + grid-template-columns: 1fr 1fr; + } + } +} diff --git a/nkebao/src/pages/traffic-pool/detail/index.tsx b/nkebao/src/pages/traffic-pool/detail/index.tsx new file mode 100644 index 00000000..7389b966 --- /dev/null +++ b/nkebao/src/pages/traffic-pool/detail/index.tsx @@ -0,0 +1,469 @@ +import React, { useState } from "react"; +import { useParams } from "react-router-dom"; +import { Card, Tabs, Tag, Avatar, Button, List, Grid } from "antd-mobile"; +import { + UserOutline, + MobileOutline, + TeamOutline, + StarFill, + MessageOutline, + EyeOutline, + ClickOutline, + PayCircleOutline, +} from "antd-mobile-icons"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import styles from "./index.module.scss"; + +// 复用类型定义和Mock数据生成函数 +import { + Device, + WechatAccount, + CustomerService, + TrafficPool, + RFMScore, + UserTag, + UserInteraction, + TrafficUser, + RFM_SEGMENTS, + generateMockDevices, + generateMockWechatAccounts, + generateMockCustomerServices, + generateMockTrafficPools, + generateRFMScore, + generateMockInteractions, + generateUserTags, +} from "../list"; + +const mockDevices = generateMockDevices(); +const mockWechatAccounts = generateMockWechatAccounts(mockDevices); +const mockCustomerServices = generateMockCustomerServices(); +const mockTrafficPools = generateMockTrafficPools(); + +// 生成Mock用户数据 +const generateMockUsers = ( + devices: Device[], + wechatAccounts: WechatAccount[], + customerServices: CustomerService[], + trafficPools: TrafficPool[] +): TrafficUser[] => { + return Array.from({ length: 500 }, (_, i) => { + const rfmScore = generateRFMScore(); + const tags = generateUserTags(rfmScore); + const interactions = generateMockInteractions(); + + const user: TrafficUser = { + id: `user-${i + 1}`, + avatar: `/placeholder.svg?height=40&width=40&query=user${Math.floor(Math.random() * 100)}`, + nickname: `用户${i + 1}`, + wechatId: `wx_${Math.random().toString(36).substr(2, 8)}`, + phone: `1${Math.floor(Math.random() * 9) + 1}${Math.random().toString().substr(2, 9)}`, + region: ["北京", "上海", "广州", "深圳", "杭州", "成都"][ + Math.floor(Math.random() * 6) + ], + note: Math.random() > 0.7 ? `这是用户${i + 1}的备注信息` : "", + status: ["pending", "added", "failed", "duplicate"][ + Math.floor(Math.random() * 4) + ] as any, + addTime: new Date( + Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000 + ).toISOString(), + source: "海报获客", + scenario: "poster", + deviceId: devices[Math.floor(Math.random() * devices.length)].id, + wechatAccountId: + wechatAccounts[Math.floor(Math.random() * wechatAccounts.length)].id, + customerServiceId: + customerServices[Math.floor(Math.random() * customerServices.length)] + .id, + poolIds: + Math.random() > 0.5 + ? [trafficPools[Math.floor(Math.random() * trafficPools.length)].id] + : [], + tags, + rfmScore, + lastInteraction: new Date( + Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000 + ).toISOString(), + totalSpent: Math.floor(Math.random() * 10000), + interactionCount: Math.floor(Math.random() * 50) + 1, + conversionRate: Math.floor(Math.random() * 100), + isDuplicate: Math.random() > 0.9, + mergedAccounts: [], + addStatus: ["not_added", "adding", "added", "failed"][ + Math.floor(Math.random() * 4) + ] as any, + interactions, + }; + + return user; + }); +}; + +const users = generateMockUsers( + mockDevices, + mockWechatAccounts, + mockCustomerServices, + mockTrafficPools +); + +const TrafficPoolDetail: React.FC = () => { + const { id } = useParams(); + const [activeTab, setActiveTab] = useState("base"); + + const user = users.find((u: TrafficUser) => u.id === id); + + if (!user) { + return ( + +
+
未找到该用户
+
+
+ ); + } + + const wechatAccount = mockWechatAccounts.find( + (acc) => acc.id === user.wechatAccountId + ); + const customerService = mockCustomerServices.find( + (cs) => cs.id === user.customerServiceId + ); + const device = mockDevices.find((device) => device.id === user.deviceId); + const rfmSegment = Object.values(RFM_SEGMENTS).find( + (seg: any) => seg.name === user.rfmScore.segment + ); + + // 辅助函数 + const getPoolNames = (poolIds: string[]) => { + return poolIds + .map((id) => mockTrafficPools.find((pool) => pool.id === id)?.name) + .filter(Boolean) + .join(", "); + }; + + const formatDate = (dateString: string) => { + if (!dateString) return "--"; + try { + const date = new Date(dateString); + return date.toLocaleDateString("zh-CN"); + } catch (error) { + return dateString; + } + }; + + const getInteractionIcon = (type: string) => { + switch (type) { + case "click": + return ; + case "message": + return ; + case "purchase": + return ; + case "view": + return ; + default: + return ; + } + }; + + const getInteractionTypeText = (type: string) => { + switch (type) { + case "click": + return "点击行为"; + case "message": + return "消息互动"; + case "purchase": + return "购买行为"; + case "view": + return "页面浏览"; + default: + return type; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case "added": + return "已添加"; + case "pending": + return "未添加"; + case "failed": + return "添加失败"; + case "duplicate": + return "重复"; + default: + return status; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "added": + return "success"; + case "pending": + return "default"; + case "failed": + return "danger"; + case "duplicate": + return "warning"; + default: + return "default"; + } + }; + + return ( + }> +
+ {/* 用户基本信息 */} + +
+ + {user.nickname?.slice(0, 2) || "用户"} + +
+
+ {user.nickname} + {user.rfmScore.priority === "high" && ( + + )} +
+
{user.wechatId}
+
+ {user.poolIds.length > 0 && ( + + {getPoolNames(user.poolIds)} + + )} + {user.status === "added" && ( + + 优先添加 + + )} +
+
+
+ + {/* RFM标签 */} +
+ {rfmSegment && ( + + {rfmSegment.name} + + )} + {user.status === "added" && ( + + 优先添加 + + )} +
+
+ + {/* Tab导航 */} + + +
+ {/* 关键信息卡片 */} + +
关键信息
+ +
+
设备
+
+ {device?.name || "--"} +
+
+
+
微信号
+
+ {wechatAccount?.nickname || "--"} +
+
+
+
客服
+
+ {customerService?.name || "--"} +
+
+
+
添加时间
+
+ {formatDate(user.addTime)} +
+
+
+
最近互动
+
+ {formatDate(user.lastInteraction)} +
+
+
+
+ + {/* RFM评分卡片 */} + +
RFM评分
+
+
+
+ {user.rfmScore.recency} +
+
最近性(R)
+
+
+
+ {user.rfmScore.frequency} +
+
频率(F)
+
+
+
+ {user.rfmScore.monetary} +
+
金额(M)
+
+
+
+ + {/* 流量池按钮 */} +
+ + +
+ + {/* 统计数据卡片 */} + + +
+
¥{user.totalSpent}
+
总消费
+
+
+
+ {user.interactionCount} +
+
互动次数
+
+
+
+ {user.conversionRate}% +
+
转化率
+
+
+
+ {getStatusText(user.status)} +
+
添加状态
+
+
+
+
+
+ + +
+ +
互动记录
+ {user.interactions && user.interactions.length > 0 ? ( + + {user.interactions.slice(0, 4).map((interaction) => ( + +
+
+ {getInteractionTypeText(interaction.type)} +
+
+ {interaction.content} + {interaction.type === "purchase" && + interaction.value && ( + + ¥{interaction.value} + + )} +
+
+ {formatDate(interaction.timestamp)}{" "} + {new Date(interaction.timestamp).toLocaleTimeString( + "zh-CN", + { + hour: "2-digit", + minute: "2-digit", + } + )} +
+
+
+ ))} +
+ ) : ( +
暂无互动记录
+ )} +
+
+
+ + +
+ +
用户标签
+
+ {user.tags.map((tag) => ( + + {tag.name} + + ))} +
+ +
+
价值标签
+
+ + 重要保持客户 + + + RFM总分: + {user.rfmScore.recency + + user.rfmScore.frequency + + user.rfmScore.monetary} + /15 + +
+
+ 价值等级: + + 高价值 + +
+
+
+ + +
+
+
+
+
+ ); +}; + +export default TrafficPoolDetail; diff --git a/nkebao/src/pages/traffic-pool/list/index.module.scss b/nkebao/src/pages/traffic-pool/list/index.module.scss new file mode 100644 index 00000000..6c5bf3c0 --- /dev/null +++ b/nkebao/src/pages/traffic-pool/list/index.module.scss @@ -0,0 +1,365 @@ +.container { + padding: 0; + background: #f5f5f5; + min-height: 100vh; +} + +.headerActions { + display: flex; + align-items: center; + gap: 8px; +} + +.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.analyticsPanel { + background: #fff; + padding: 16px; + margin-bottom: 8px; +} + +.statsGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 16px; +} + +.statCard { + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: space-between; +} + +.statContent { + flex: 1; +} + +.statValue { + font-size: 24px; + font-weight: bold; + color: #1677ff; + line-height: 1; + margin-bottom: 4px; +} + +.statLabel { + font-size: 12px; + color: #666; +} + +.statIcon { + font-size: 24px; + color: #1677ff; +} + +.efficiencyCard { + padding: 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.efficiencyTitle { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 12px; +} + +.efficiencyGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 16px; +} + +.efficiencyItem { + text-align: center; +} + +.efficiencyValue { + font-size: 18px; + font-weight: bold; + color: #1677ff; + line-height: 1; + margin-bottom: 4px; +} + +.efficiencyLabel { + font-size: 12px; + color: #666; +} + +.statusGrid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 8px; + padding-top: 12px; + border-top: 1px solid #f0f0f0; +} + +.statusItem { + text-align: center; +} + +.statusValue { + font-size: 16px; + font-weight: bold; + line-height: 1; + margin-bottom: 4px; +} + +.statusLabel { + font-size: 12px; + color: #666; +} + +.statusItem:nth-child(1) .statusValue { + color: #52c41a; +} + +.statusItem:nth-child(2) .statusValue { + color: #faad14; +} + +.statusItem:nth-child(3) .statusValue { + color: #ff4d4f; +} + +.searchSection { + background: #fff; + padding: 12px 16px; + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; +} + +.filterButton { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 4px; + padding: 8px 12px; + border-radius: 6px; + font-size: 14px; +} + +.actionBar { + background: #fff; + padding: 12px 16px; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; + border-bottom: 1px solid #f0f0f0; +} + +.selectSection { + display: flex; + align-items: center; + gap: 12px; +} + +.addButton { + font-size: 12px; + padding: 4px 8px; + height: 28px; +} + +.totalCount { + font-size: 12px; + color: #666; +} + +.userList { + background: #fff; + margin: 0; + padding: 0; +} + +.userItem { + padding: 16px; + border-bottom: 1px solid #f0f0f0; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background-color: #fafafa; + } + + &:last-child { + border-bottom: none; + } +} + +.userCheckbox { + margin-right: 12px; +} + +.userContent { + flex: 1; + min-width: 0; +} + +.userHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 8px; +} + +.userInfo { + display: flex; + align-items: center; + gap: 12px; + flex: 1; + min-width: 0; +} + +.userAvatar { + width: 48px; + height: 48px; + border-radius: 50%; + flex-shrink: 0; +} + +.userDetails { + flex: 1; + min-width: 0; +} + +.userName { + font-size: 16px; + font-weight: 500; + color: #333; + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 4px; + line-height: 1.2; +} + +.starIcon { + color: #ff4d4f; + font-size: 14px; +} + +.userWechatId { + font-size: 14px; + color: #1677ff; + font-weight: 500; +} + +.userMeta { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 8px; + font-size: 12px; + color: #666; +} + +.metaItem { + display: flex; + align-items: center; + gap: 4px; +} + +.userTags { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 8px; +} + +.poolInfo { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: #666; +} + +.filterPopup { + height: 100vh; + display: flex; + flex-direction: column; + background: #fff; +} + +.filterHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid #f0f0f0; + font-size: 16px; + font-weight: 500; +} + +.filterContent { + flex: 1; + padding: 16px; + overflow-y: auto; +} + +.filterItem { + margin-bottom: 24px; +} + +.filterLabel { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 8px; +} + +.filterActions { + display: flex; + gap: 12px; + padding: 16px; + border-top: 1px solid #f0f0f0; + margin-top: auto; + + .adm-button { + flex: 1; + } +} + +// 响应式设计 +@media (max-width: 375px) { + .statsGrid { + grid-template-columns: 1fr; + } + + .efficiencyGrid { + grid-template-columns: 1fr; + } + + .statusGrid { + grid-template-columns: 1fr 1fr; + } + + .userMeta { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } +} diff --git a/nkebao/src/pages/traffic-pool/list/index.tsx b/nkebao/src/pages/traffic-pool/list/index.tsx new file mode 100644 index 00000000..f4d1da91 --- /dev/null +++ b/nkebao/src/pages/traffic-pool/list/index.tsx @@ -0,0 +1,943 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { useNavigate } from "react-router-dom"; +import { + Card, + List, + Button, + SearchBar, + Checkbox, + Tag, + Avatar, + Toast, + SpinLoading, + Popup, + Selector, + InfiniteScroll, +} from "antd-mobile"; +import { + SearchOutline, + FilterOutline, + RefreshOutline, + StarOutline, + StarFill, + UserOutline, + MobileOutline, + TeamOutline, + BarChartOutline, + ChevronDownOutline, + ChevronUpOutline, +} from "antd-mobile-icons"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import styles from "./index.module.scss"; + +// 类型定义 +export interface Device { + id: string; + name: string; + status: "online" | "offline" | "busy"; + battery: number; + location: string; + wechatAccounts: number; + dailyAddLimit: number; + todayAdded: number; +} + +export interface WechatAccount { + id: string; + nickname: string; + wechatId: string; + avatar: string; + deviceId: string; + status: "normal" | "limited" | "blocked"; + friendCount: number; + dailyAddLimit: number; +} + +export interface CustomerService { + id: string; + name: string; + avatar: string; + status: "online" | "offline" | "busy"; + assignedUsers: number; +} + +export interface TrafficPool { + id: string; + name: string; + description: string; + userCount: number; + tags: string[]; + createdAt: string; +} + +export interface RFMScore { + recency: number; + frequency: number; + monetary: number; + total: number; + segment: string; + priority: "high" | "medium" | "low"; +} + +export interface UserTag { + id: string; + name: string; + color: string; + source: string; +} + +export interface UserInteraction { + id: string; + type: "message" | "purchase" | "view" | "click"; + content: string; + timestamp: string; + value?: number; +} + +export interface TrafficUser { + id: string; + avatar: string; + nickname: string; + wechatId: string; + phone: string; + region: string; + note: string; + status: "pending" | "added" | "failed" | "duplicate"; + addTime: string; + source: string; + scenario: string; + deviceId: string; + wechatAccountId: string; + customerServiceId: string; + poolIds: string[]; + tags: UserTag[]; + rfmScore: RFMScore; + lastInteraction: string; + totalSpent: number; + interactionCount: number; + conversionRate: number; + isDuplicate: boolean; + mergedAccounts: string[]; + addStatus: "not_added" | "adding" | "added" | "failed"; + interactions: UserInteraction[]; +} + +// 常量定义 +export const SCENARIOS = [ + { id: "poster", name: "海报获客", icon: "🎨" }, + { id: "phone", name: "电话获客", icon: "📞" }, + { id: "douyin", name: "抖音获客", icon: "🎵" }, + { id: "xiaohongshu", name: "小红书获客", icon: "📖" }, + { id: "weixinqun", name: "微信群获客", icon: "👥" }, + { id: "api", name: "API获客", icon: "🔗" }, + { id: "order", name: "订单获客", icon: "📦" }, + { id: "payment", name: "付款码获客", icon: "💳" }, +]; + +export const RFM_SEGMENTS = { + "555": { + name: "重要价值客户", + color: "red", + icon: "👑", + priority: "high", + }, + "554": { + name: "重要保持客户", + color: "purple", + icon: "💎", + priority: "high", + }, + "544": { + name: "重要发展客户", + color: "blue", + icon: "🚀", + priority: "high", + }, + "455": { + name: "重要挽留客户", + color: "orange", + icon: "⚠️", + priority: "medium", + }, + "444": { + name: "一般价值客户", + color: "green", + icon: "👤", + priority: "medium", + }, + "333": { + name: "一般保持客户", + color: "yellow", + icon: "📈", + priority: "medium", + }, + "222": { + name: "新用户", + color: "cyan", + icon: "🌟", + priority: "low", + }, + "111": { + name: "流失预警客户", + color: "gray", + icon: "😴", + priority: "low", + }, +} as const; + +// Mock数据生成函数 +const generateMockDevices = (): Device[] => { + return Array.from({ length: 8 }, (_, i) => ({ + id: `device-${i + 1}`, + name: `设备${i + 1}`, + status: ["online", "offline", "busy"][Math.floor(Math.random() * 3)] as + | "online" + | "offline" + | "busy", + battery: Math.floor(Math.random() * 100), + location: ["北京", "上海", "广州", "深圳"][Math.floor(Math.random() * 4)], + wechatAccounts: Math.floor(Math.random() * 5) + 1, + dailyAddLimit: Math.random() > 0.5 ? 20 : 10, + todayAdded: Math.floor(Math.random() * 15), + })); +}; + +const generateMockWechatAccounts = (devices: Device[]): WechatAccount[] => { + const accounts: WechatAccount[] = []; + devices.forEach((device) => { + for (let i = 0; i < device.wechatAccounts; i++) { + accounts.push({ + id: `wx-${device.id}-${i + 1}`, + nickname: `微信${device.id.split("-")[1]}-${i + 1}`, + wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`, + avatar: `/placeholder.svg?height=40&width=40&query=wx${Math.floor(Math.random() * 10)}`, + deviceId: device.id, + status: ["normal", "limited", "blocked"][ + Math.floor(Math.random() * 3) + ] as "normal" | "limited" | "blocked", + friendCount: Math.floor(Math.random() * 4000) + 1000, + dailyAddLimit: Math.random() > 0.5 ? 20 : 10, + }); + } + }); + return accounts; +}; + +const generateMockCustomerServices = (): CustomerService[] => { + return Array.from({ length: 5 }, (_, i) => ({ + id: `cs-${i + 1}`, + name: `客服${i + 1}`, + avatar: `/placeholder.svg?height=40&width=40&query=cs${i}`, + status: ["online", "offline", "busy"][Math.floor(Math.random() * 3)] as + | "online" + | "offline" + | "busy", + assignedUsers: Math.floor(Math.random() * 100) + 50, + })); +}; + +const generateMockTrafficPools = (): TrafficPool[] => { + return [ + { + id: "pool-1", + name: "高价值客户池", + description: "包含所有高价值客户,优先添加", + userCount: 156, + tags: ["高价值", "优先添加", "重要客户"], + createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), + }, + { + id: "pool-2", + name: "潜在客户池", + description: "有潜力的用户,需要进一步培养", + userCount: 289, + tags: ["潜在客户", "需培养"], + createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000).toISOString(), + }, + { + id: "pool-3", + name: "新用户池", + description: "新注册或新添加的用户", + userCount: 432, + tags: ["新用户", "待分类"], + createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), + }, + ]; +}; + +const generateRFMScore = (): RFMScore => { + const recency = Math.floor(Math.random() * 5) + 1; + const frequency = Math.floor(Math.random() * 5) + 1; + const monetary = Math.floor(Math.random() * 5) + 1; + const total = recency + frequency + monetary; + + let segment: string; + let priority: "high" | "medium" | "low"; + + if (total >= 12) { + segment = Object.values(RFM_SEGMENTS)[Math.floor(Math.random() * 3)].name; + priority = "high"; + } else if (total >= 8) { + segment = + Object.values(RFM_SEGMENTS)[3 + Math.floor(Math.random() * 3)].name; + priority = "medium"; + } else { + segment = + Object.values(RFM_SEGMENTS)[6 + Math.floor(Math.random() * 2)].name; + priority = "low"; + } + + return { recency, frequency, monetary, total, segment, priority }; +}; + +const generateMockInteractions = (): UserInteraction[] => { + const types = ["message", "purchase", "view", "click"] as const; + return Array.from({ length: Math.floor(Math.random() * 10) + 1 }, (_, i) => { + const type = types[Math.floor(Math.random() * types.length)]; + return { + id: `interaction-${i + 1}`, + type, + content: + type === "message" + ? "用户发送了消息" + : type === "purchase" + ? "用户购买了产品" + : type === "view" + ? "用户查看了产品" + : "用户点击了链接", + timestamp: new Date( + Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000 + ).toISOString(), + value: + type === "purchase" + ? Math.floor(Math.random() * 1000) + 100 + : undefined, + }; + }); +}; + +const generateUserTags = (rfmScore: RFMScore): UserTag[] => { + const allTags = [ + { id: "tag-1", name: "活跃用户", color: "success", source: "system" }, + { id: "tag-2", name: "高消费", color: "danger", source: "system" }, + { id: "tag-3", name: "忠实客户", color: "primary", source: "system" }, + { id: "tag-4", name: "新用户", color: "warning", source: "system" }, + { id: "tag-5", name: "VIP客户", color: "purple", source: "manual" }, + { id: "tag-6", name: "潜在客户", color: "default", source: "system" }, + ]; + + const tags: UserTag[] = []; + + if (rfmScore.priority === "high") { + tags.push(allTags[1], allTags[2]); + if (Math.random() > 0.5) tags.push(allTags[4]); + } else if (rfmScore.priority === "medium") { + tags.push(allTags[0]); + if (Math.random() > 0.5) tags.push(allTags[5]); + } else { + tags.push(allTags[3]); + if (Math.random() > 0.3) tags.push(allTags[5]); + } + + return tags; +}; + +const mockDevices = generateMockDevices(); +const mockWechatAccounts = generateMockWechatAccounts(mockDevices); +const mockCustomerServices = generateMockCustomerServices(); +const mockTrafficPools = generateMockTrafficPools(); + +const generateMockUsers = ( + devices: Device[], + wechatAccounts: WechatAccount[], + customerServices: CustomerService[], + trafficPools: TrafficPool[] +): TrafficUser[] => { + return Array.from({ length: 500 }, (_, i) => { + const rfmScore = generateRFMScore(); + const tags = generateUserTags(rfmScore); + const interactions = generateMockInteractions(); + + const user: TrafficUser = { + id: `user-${i + 1}`, + avatar: `/placeholder.svg?height=40&width=40&query=user${Math.floor(Math.random() * 100)}`, + nickname: `用户${i + 1}`, + wechatId: `wx_${Math.random().toString(36).substr(2, 8)}`, + phone: `1${Math.floor(Math.random() * 9) + 1}${Math.random().toString().substr(2, 9)}`, + region: ["北京", "上海", "广州", "深圳", "杭州", "成都"][ + Math.floor(Math.random() * 6) + ], + note: Math.random() > 0.7 ? `这是用户${i + 1}的备注信息` : "", + status: ["pending", "added", "failed", "duplicate"][ + Math.floor(Math.random() * 4) + ] as any, + addTime: new Date( + Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000 + ).toISOString(), + source: SCENARIOS[Math.floor(Math.random() * SCENARIOS.length)].name, + scenario: SCENARIOS[Math.floor(Math.random() * SCENARIOS.length)].id, + deviceId: devices[Math.floor(Math.random() * devices.length)].id, + wechatAccountId: + wechatAccounts[Math.floor(Math.random() * wechatAccounts.length)].id, + customerServiceId: + customerServices[Math.floor(Math.random() * customerServices.length)] + .id, + poolIds: + Math.random() > 0.5 + ? [trafficPools[Math.floor(Math.random() * trafficPools.length)].id] + : [], + tags, + rfmScore, + lastInteraction: new Date( + Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000 + ).toISOString(), + totalSpent: Math.floor(Math.random() * 10000), + interactionCount: Math.floor(Math.random() * 50) + 1, + conversionRate: Math.floor(Math.random() * 100), + isDuplicate: Math.random() > 0.9, + mergedAccounts: [], + addStatus: ["not_added", "adding", "added", "failed"][ + Math.floor(Math.random() * 4) + ] as any, + interactions, + }; + + return user; + }); +}; + +const TrafficPoolList: React.FC = () => { + const navigate = useNavigate(); + + // 基础数据状态 + const [users, setUsers] = useState([]); + const [devices] = useState(mockDevices); + const [wechatAccounts] = useState(mockWechatAccounts); + const [customerServices] = useState(mockCustomerServices); + const [trafficPools] = useState(mockTrafficPools); + + // UI状态 + const [loading, setLoading] = useState(false); + const [selectedUsers, setSelectedUsers] = useState([]); + const [showFilters, setShowFilters] = useState(false); + const [showAnalytics, setShowAnalytics] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [page, setPage] = useState(1); + + // 筛选状态 + const [deviceFilter, setDeviceFilter] = useState("all"); + const [poolFilter, setPoolFilter] = useState("all"); + const [valuationFilter, setValuationFilter] = useState("all"); + const [statusFilter, setStatusFilter] = useState("all"); + const [searchQuery, setSearchQuery] = useState(""); + + // 初始化数据 + useEffect(() => { + const mockUsers = generateMockUsers( + devices, + wechatAccounts, + customerServices, + trafficPools + ); + setUsers(mockUsers); + }, [devices, wechatAccounts, customerServices, trafficPools]); + + // 计算统计数据 + const stats = { + total: users.length, + highValue: users.filter((u) => u.rfmScore.priority === "high").length, + mediumValue: users.filter((u) => u.rfmScore.priority === "medium").length, + lowValue: users.filter((u) => u.rfmScore.priority === "low").length, + duplicates: users.filter((u) => u.isDuplicate).length, + pending: users.filter((u) => u.status === "pending").length, + added: users.filter((u) => u.status === "added").length, + failed: users.filter((u) => u.status === "failed").length, + avgSpent: Math.round( + users.reduce((sum, u) => sum + u.totalSpent, 0) / users.length + ), + addSuccessRate: Math.round( + (users.filter((u) => u.status === "added").length / users.length) * 100 + ), + duplicateRate: Math.round( + (users.filter((u) => u.isDuplicate).length / users.length) * 100 + ), + }; + + // 过滤用户 + const filteredUsers = users.filter((user) => { + const matchesSearch = + !searchQuery || + user.nickname.toLowerCase().includes(searchQuery.toLowerCase()) || + user.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) || + user.phone.includes(searchQuery); + + const matchesDevice = + deviceFilter === "all" || user.deviceId === deviceFilter; + const matchesValuation = + valuationFilter === "all" || user.rfmScore.priority === valuationFilter; + const matchesStatus = + statusFilter === "all" || user.status === statusFilter; + const matchesPool = + poolFilter === "all" || + (poolFilter === "none" + ? user.poolIds.length === 0 + : user.poolIds.includes(poolFilter)); + + return ( + matchesSearch && + matchesDevice && + matchesValuation && + matchesStatus && + matchesPool + ); + }); + + // 按优先级排序 + const sortedUsers = filteredUsers.sort((a, b) => { + const priorityOrder = { high: 3, medium: 2, low: 1 }; + return ( + priorityOrder[b.rfmScore.priority] - priorityOrder[a.rfmScore.priority] + ); + }); + + // 分页数据 + const pageSize = 20; + const paginatedUsers = sortedUsers.slice(0, page * pageSize); + + // 处理用户选择 + const handleUserSelect = useCallback((userId: string, checked: boolean) => { + setSelectedUsers((prev) => + checked ? [...prev, userId] : prev.filter((id) => id !== userId) + ); + }, []); + + // 处理全选 + const handleSelectAll = useCallback( + (checked: boolean) => { + if (checked) { + setSelectedUsers(paginatedUsers.map((user) => user.id)); + } else { + setSelectedUsers([]); + } + }, + [paginatedUsers] + ); + + // 重置筛选器 + const resetFilters = useCallback(() => { + setDeviceFilter("all"); + setPoolFilter("all"); + setValuationFilter("all"); + setStatusFilter("all"); + setSearchQuery(""); + setShowFilters(false); + }, []); + + // 刷新数据 + const handleRefresh = useCallback(() => { + setLoading(true); + setTimeout(() => { + const refreshedUsers = generateMockUsers( + devices, + wechatAccounts, + customerServices, + trafficPools + ); + setUsers(refreshedUsers); + setLoading(false); + Toast.show({ content: "刷新成功" }); + }, 800); + }, [devices, wechatAccounts, customerServices, trafficPools]); + + // 添加到流量池 + const handleAddToPool = useCallback(() => { + if (selectedUsers.length === 0) { + Toast.show({ content: "请先选择要添加到流量池的用户" }); + return; + } + Toast.show({ content: `已将 ${selectedUsers.length} 个用户添加到流量池` }); + setSelectedUsers([]); + }, [selectedUsers.length]); + + // 加载更多 + const loadMore = async () => { + if (page * pageSize >= sortedUsers.length) { + setHasMore(false); + return; + } + setPage((prev) => prev + 1); + }; + + // 辅助函数 + const getWechatAccount = (accountId: string) => { + return wechatAccounts.find((acc) => acc.id === accountId); + }; + + const getCustomerService = (csId: string) => { + return customerServices.find((cs) => cs.id === csId); + }; + + const getDevice = (deviceId: string) => { + return devices.find((device) => device.id === deviceId); + }; + + const getPoolNames = (poolIds: string[]) => { + return poolIds + .map((id) => trafficPools.find((pool) => pool.id === id)?.name) + .filter(Boolean) + .join(", "); + }; + + const formatDate = (dateString: string) => { + if (!dateString) return "--"; + try { + const date = new Date(dateString); + return date.toLocaleDateString("zh-CN"); + } catch (error) { + return dateString; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case "added": + return "已添加"; + case "pending": + return "未添加"; + case "failed": + return "添加失败"; + case "duplicate": + return "重复"; + default: + return status; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "added": + return "success"; + case "pending": + return "default"; + case "failed": + return "danger"; + case "duplicate": + return "warning"; + default: + return "default"; + } + }; + + return ( + + + +
+ } + /> + } + loading={loading} + > +
+ {/* 数据分析面板 */} + {showAnalytics && ( +
+
+ +
+
{filteredUsers.length}
+
总用户数
+
+ +
+ +
+
{stats.highValue}
+
高价值用户
+
+ +
+
+ +
添加效率
+
+
+
+ {stats.addSuccessRate}% +
+
成功率
+
+
+
+ ¥{stats.avgSpent} +
+
平均消费
+
+
+
+
+
{stats.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/traffic-pool.tsx b/nkebao/src/router/module/traffic-pool.tsx index d0dcd70d..99872ab7 100644 --- a/nkebao/src/router/module/traffic-pool.tsx +++ b/nkebao/src/router/module/traffic-pool.tsx @@ -1,5 +1,5 @@ -import TrafficPool from "@/pages/traffic-pool/TrafficPool"; -import TrafficPoolDetail from "@/pages/traffic-pool/TrafficPoolDetail"; +import TrafficPool from "@/pages/traffic-pool/list/index"; +import TrafficPoolDetail from "@/pages/traffic-pool/detail/index"; const trafficPoolRoutes = [ {