diff --git a/nkebao/src/pages/mine/traffic-pool/TrafficPool.tsx b/nkebao/src/pages/mine/traffic-pool/TrafficPool.tsx deleted file mode 100644 index d64714d2..00000000 --- a/nkebao/src/pages/mine/traffic-pool/TrafficPool.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const TrafficPool: React.FC = () => { - return ( - - ); -}; - -export default TrafficPool; diff --git a/nkebao/src/pages/mine/traffic-pool/TrafficPoolDetail.tsx b/nkebao/src/pages/mine/traffic-pool/TrafficPoolDetail.tsx deleted file mode 100644 index d4c1ceef..00000000 --- a/nkebao/src/pages/mine/traffic-pool/TrafficPoolDetail.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; - -const TrafficPoolDetail: React.FC = () => { - return ; -}; - -export default TrafficPoolDetail; diff --git a/nkebao/src/pages/mine/traffic-pool/list/api.ts b/nkebao/src/pages/mine/traffic-pool/list/api.ts index e69de29b..71c2e5c2 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/api.ts +++ b/nkebao/src/pages/mine/traffic-pool/list/api.ts @@ -0,0 +1,30 @@ +import request from "@/api/request"; +import type { TrafficPoolListResponse } from "./data"; + +// 获取流量池列表 +export function fetchTrafficPoolList(params: { + page?: number; + pageSize?: number; + keyword?: string; +}) { + return request("/v1/traffic/pool", params, "GET"); +} + +// 获取设备列表(如无真实接口可用mock) +export async function fetchDeviceOptions(): Promise { + // TODO: 替换为真实接口 + return [ + { id: "device-1", name: "设备1" }, + { id: "device-2", name: "设备2" }, + { id: "device-3", name: "设备3" }, + ]; +} + +// 获取分组列表(如无真实接口可用mock) +export async function fetchPackageOptions(): Promise { + // TODO: 替换为真实接口 + return [ + { id: "pkg-1", name: "高价值客户池" }, + { id: "pkg-2", name: "测试流量池" }, + ]; +} diff --git a/nkebao/src/pages/mine/traffic-pool/list/data.ts b/nkebao/src/pages/mine/traffic-pool/list/data.ts index e69de29b..17e8face 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/data.ts +++ b/nkebao/src/pages/mine/traffic-pool/list/data.ts @@ -0,0 +1,45 @@ +// 流量池用户类型 +export interface TrafficPoolUser { + id: number; + identifier: string; + mobile: string; + wechatId: string; + fromd: string; + status: number; + createTime: string; + companyId: number; + sourceId: string; + type: number; + nickname: string; + avatar: string; + gender: number; + phone: string; + packages: string[]; + tags: string[]; +} + +// 列表响应类型 +export interface TrafficPoolUserListResponse { + list: TrafficPoolUser[]; + total: number; + page: number; + pageSize: number; +} + +// 设备类型 +export interface DeviceOption { + id: string; + name: string; +} + +// 分组类型 +export interface PackageOption { + id: string; + name: string; +} + +// 用户价值类型 +export type ValueLevel = "all" | "high" | "medium" | "low"; + +// 状态类型 +export type UserStatus = "all" | "added" | "pending" | "failed" | "duplicate"; 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 e69de29b..f37ab2a7 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/index.module.scss +++ b/nkebao/src/pages/mine/traffic-pool/list/index.module.scss @@ -0,0 +1,50 @@ +.listWrap { + padding: 12px; +} + +.card { + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.04); + padding: 16px; + margin-bottom: 12px; +} + +.title { + font-size: 16px; + font-weight: 600; + color: #222; +} + +.desc { + font-size: 13px; + color: #888; + margin: 6px 0 4px 0; +} + +.count { + font-size: 13px; + color: #1677ff; +} + +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + margin: 16px 0; +} + +.pagination button { + background: #f5f5f5; + border: none; + border-radius: 4px; + padding: 4px 12px; + color: #1677ff; + cursor: pointer; +} + +.pagination button:disabled { + color: #ccc; + cursor: not-allowed; +} diff --git a/nkebao/src/pages/mine/traffic-pool/list/index.tsx b/nkebao/src/pages/mine/traffic-pool/list/index.tsx index 94006840..3f8bf42c 100644 --- a/nkebao/src/pages/mine/traffic-pool/list/index.tsx +++ b/nkebao/src/pages/mine/traffic-pool/list/index.tsx @@ -1,3 +1,439 @@ -export default function TrafficPoolList() { - return
TrafficPoolList
; -} +import React, { useEffect, useState } from "react"; +import Layout from "@/components/Layout/Layout"; +import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Input } from "antd"; +import { fetchTrafficPoolList } from "./api"; +import type { TrafficPoolUser } from "./data"; +import styles from "./index.module.scss"; +import { + List, + Empty, + Avatar, + Button, + Modal, + Selector, + Toast, + Card, +} from "antd-mobile"; +import { fetchDeviceOptions, fetchPackageOptions } from "./api"; +import type { + DeviceOption, + PackageOption, + ValueLevel, + UserStatus, +} from "./data"; +import { useNavigate } from "react-router-dom"; +import NavCommon from "@/components/NavCommon"; + +const defaultAvatar = + "https://cdn.jsdelivr.net/gh/maokaka/static/avatar-default.png"; + +const valueLevelOptions = [ + { label: "全部", value: "all" }, + { label: "高价值", value: "high" }, + { label: "中价值", value: "medium" }, + { label: "低价值", value: "low" }, +]; +const statusOptions = [ + { label: "全部", value: "all" }, + { label: "已添加", value: "added" }, + { label: "待添加", value: "pending" }, + { label: "添加失败", value: "failed" }, + { label: "重复", value: "duplicate" }, +]; + +const TrafficPoolList: React.FC = () => { + const [loading, setLoading] = useState(false); + const [list, setList] = useState([]); + const [page, setPage] = useState(1); + const [pageSize] = useState(10); + const [total, setTotal] = useState(0); + const [search, setSearch] = useState(""); + const [showFilter, setShowFilter] = useState(false); + const [deviceOptions, setDeviceOptions] = useState([]); + const [packageOptions, setPackageOptions] = useState([]); + const [deviceId, setDeviceId] = useState("all"); + const [packageId, setPackageId] = useState("all"); + const [valueLevel, setValueLevel] = useState("all"); + const [userStatus, setUserStatus] = useState("all"); + const [selectedIds, setSelectedIds] = useState([]); + const [batchModal, setBatchModal] = useState(false); + const [batchTarget, setBatchTarget] = useState(""); + const [showStats, setShowStats] = useState(false); + const navigate = useNavigate(); + + // 统计数据 + const stats = React.useMemo(() => { + const total = list.length; + const highValue = list.filter((u) => + u.tags.includes("高价值客户池") + ).length; + const added = list.filter((u) => u.status === 1).length; + const pending = list.filter((u) => u.status === 0).length; + const failed = list.filter((u) => u.status === -1).length; + const addSuccessRate = total ? Math.round((added / total) * 100) : 0; + return { total, highValue, added, pending, failed, addSuccessRate }; + }, [list]); + + const getList = async () => { + setLoading(true); + try { + const res = await fetchTrafficPoolList({ + page, + pageSize, + keyword: search, + }); + setList(res.list || []); + setTotal(res.total || 0); + } finally { + setLoading(false); + } + }; + + // 获取筛选项 + useEffect(() => { + fetchDeviceOptions().then(setDeviceOptions); + fetchPackageOptions().then(setPackageOptions); + }, []); + + // 筛选条件变化时刷新列表 + useEffect(() => { + getList(); + // eslint-disable-next-line + }, [page, search, deviceId, packageId, valueLevel, userStatus]); + + // 全选/反选 + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedIds(list.map((item) => item.id)); + } else { + setSelectedIds([]); + } + }; + // 单选 + const handleSelect = (id: number, checked: boolean) => { + setSelectedIds((prev) => + checked ? [...prev, id] : prev.filter((i) => i !== id) + ); + }; + + // 批量加入分组/流量池 + const handleBatchAdd = () => { + if (!batchTarget) { + Toast.show({ content: "请选择目标分组", position: "top" }); + return; + } + // TODO: 调用后端批量接口,这里仅模拟 + Toast.show({ + content: `已将${selectedIds.length}个用户加入${packageOptions.find((p) => p.id === batchTarget)?.name || ""}`, + position: "top", + }); + setBatchModal(false); + setSelectedIds([]); + setBatchTarget(""); + // 可刷新列表 + }; + + // 性别icon + const renderGender = (gender: number) => { + if (gender === 1) + return ; + if (gender === 2) + return ; + return ?; + }; + + return ( + + setShowStats((s) => !s)} + style={{ marginLeft: 8 }} + > + {showStats ? "收起分析" : "数据分析"} + + } + /> + {/* 搜索栏 */} +
+
+ setSearch(e.target.value)} + prefix={} + allowClear + size="large" + /> +
+ +
+ + } + > + {/* 数据分析面板 */} + {showStats && ( +
+
+ +
+ {stats.total} +
+
总用户数
+
+ +
+ {stats.highValue} +
+
高价值用户
+
+
+
+ +
+ {stats.addSuccessRate}% +
+
添加成功率
+
+ +
+ {stats.added} +
+
已添加
+
+ +
+ {stats.pending} +
+
待添加
+
+ +
+ {stats.failed} +
+
添加失败
+
+
+
+ )} + {/* 批量操作栏 */} +
+ 0} + onChange={(e) => handleSelectAll(e.target.checked)} + style={{ marginRight: 8 }} + /> + 全选 + {selectedIds.length > 0 && ( + <> + {`已选${selectedIds.length}项`} + + + )} + +
+ {/* 批量加入分组弹窗 */} + setBatchModal(false)} + footer={[ + { text: "取消", onClick: () => setBatchModal(false) }, + { text: "确定", onClick: handleBatchAdd }, + ]} + > +
+
选择目标分组
+ ({ + label: p.name, + value: p.id, + }))} + value={[batchTarget]} + onChange={(v) => setBatchTarget(v[0])} + /> +
+
+ 将选中的{selectedIds.length}个用户加入所选分组 +
+
+ {/* 筛选弹窗 */} + setShowFilter(false)} + footer={[ + { + text: "重置", + onClick: () => { + setDeviceId("all"); + setPackageId("all"); + setValueLevel("all"); + setUserStatus("all"); + }, + }, + { text: "确定", onClick: () => setShowFilter(false) }, + ]} + > +
+
设备
+ ({ label: d.name, value: d.id })), + ]} + value={[deviceId]} + onChange={(v) => setDeviceId(v[0])} + /> +
+
+
分组
+ ({ label: p.name, value: p.id })), + ]} + value={[packageId]} + onChange={(v) => setPackageId(v[0])} + /> +
+
+
用户价值
+ setValueLevel(v[0] as ValueLevel)} + /> +
+
+
状态
+ setUserStatus(v[0] as UserStatus)} + /> +
+
+
+ {list.length === 0 && !loading ? ( + + ) : ( + + {list.map((item) => ( + +
+ navigate(`/mine/traffic-pool/detail/${item.id}`) + } + > +
+ handleSelect(item.id, e.target.checked)} + style={{ marginRight: 8 }} + onClick={(e) => e.stopPropagation()} + /> + +
+
+ {item.nickname || item.identifier} + {renderGender(item.gender)} +
+
+ 微信号:{item.wechatId || "-"} +
+
+ 来源:{item.fromd || "-"} +
+
+ 分组: + {item.packages && item.packages.length + ? item.packages.join(",") + : "-"} +
+
+ 创建时间:{item.createTime} +
+
+
+
+
+ ))} +
+ )} +
+ {/* 分页 */} + {total > pageSize && ( +
+ + + {page} / {Math.ceil(total / pageSize)} + + +
+ )} +
+ ); +}; + +export default TrafficPoolList;