diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx index c5c6306d..16d2abe7 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx @@ -1,29 +1,19 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import Layout from "@/components/Layout/Layout"; -import { - SearchOutlined, - ReloadOutlined, - BarChartOutlined, -} from "@ant-design/icons"; -import { Toast } from "antd-mobile"; -import { Input, Button, Checkbox, Pagination } from "antd"; +import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Input, Button, Pagination } from "antd"; import styles from "./index.module.scss"; import { Empty, Avatar } from "antd-mobile"; import { useNavigate } from "react-router-dom"; import NavCommon from "@/components/NavCommon"; -import { fetchTrafficPoolList, fetchScenarioOptions, addPackage } from "./api"; -import type { TrafficPoolUser, ScenarioOption } from "./data"; -import DataAnalysisPanel from "./DataAnalysisPanel"; -import FilterModal from "./FilterModal"; -import BatchAddModal from "./BatchAddModal"; -import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; +import { fetchTrafficPoolList } from "./api"; +import type { TrafficPoolUser } from "./data"; const defaultAvatar = "https://cdn.jsdelivr.net/gh/maokaka/static/avatar-default.png"; const TrafficPoolList: React.FC = () => { const navigate = useNavigate(); - // 基础状态 const [loading, setLoading] = useState(false); const [list, setList] = useState([]); const [page, setPage] = useState(1); @@ -31,251 +21,57 @@ const TrafficPoolList: React.FC = () => { const [total, setTotal] = useState(0); const [search, setSearch] = useState(""); - // 筛选相关 - const [showFilter, setShowFilter] = useState(false); - const [scenarioOptions, setScenarioOptions] = useState([]); + const handleSearch = (value: string) => { + setSearch(value); + setPage(1); + }; - // 公共筛选条件状态 - const [filterParams, setFilterParams] = useState({ - selectedDevices: [] as DeviceSelectionItem[], - packageId: 0, - scenarioId: 0, - userValue: 0, - userStatus: 0, - }); + useEffect(() => { + const fetchData = async () => { + setLoading(true); + try { + const params = { + page, + pageSize, + keyword: search, + }; - // 批量相关 - const [selectedIds, setSelectedIds] = useState([]); - const [batchModal, setBatchModal] = useState(false); - - // 数据分析 - const [showStats, setShowStats] = useState(false); - - // 获取列表 - const getList = async (customParams?: any) => { - setLoading(true); - try { - const params: any = { - page, - pageSize, - keyword: search, - packageld: filterParams.packageId, - sceneId: filterParams.scenarioId, - userValue: filterParams.userValue, - addStatus: filterParams.userStatus, - deviceld: filterParams.selectedDevices.map(d => d.id).join(), - ...customParams, // 允许传入自定义参数覆盖 - }; - - const res = await fetchTrafficPoolList(params); - setList(res.list || []); - setTotal(res.total || 0); - } catch (error) { - // 忽略请求过于频繁的错误,避免页面崩溃 - if (error !== "请求过于频繁,请稍后再试") { + const res = await fetchTrafficPoolList(params); + setList(res.list || []); + setTotal(res.total || 0); + } catch (error) { console.error("获取列表失败:", error); + } finally { + setLoading(false); } - } finally { - setLoading(false); - } - }; + }; - // 获取筛选项 - useEffect(() => { - fetchScenarioOptions().then(res => { - setScenarioOptions(res.list || []); - }); - }, []); - - // 全选/反选 - 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 = async options => { - try { - // 构建请求参数 - const params = { - type: "2", // 2选择用户 - addPackageId: options.selectedPackageId, // 目标分组ID - userIds: selectedIds.map(id => id), // 选中的用户ID数组 - // 如果有当前筛选条件,也可以传递 - ...(filterParams.packageId && { - packageId: filterParams.packageId, - }), - ...(filterParams.scenarioId && { - taskId: filterParams.scenarioId, - }), - ...(filterParams.userValue && { - userValue: filterParams.userValue, - }), - ...(filterParams.userStatus && { - addStatus: filterParams.userStatus, - }), - ...(filterParams.selectedDevices.length > 0 && { - deviceId: filterParams.selectedDevices.map(d => d.id).join(","), - }), - ...(search && { keyword: search }), - }; - - console.log("批量加入请求参数:", params); - - // 调用接口 - const result = await addPackage(params); - console.log("批量加入结果:", result); - - // 成功后刷新列表 - getList(); - - // 关闭弹窗并清空选择 - setBatchModal(false); - setSelectedIds([]); - - // 可以添加成功提示 - Toast.show({ - content: `成功将用户加入分组`, - position: "top", - }); - } catch (error) { - console.error("批量加入失败:", error); - // 可以添加错误提示 - Toast.show({ content: "批量加入失败,请重试", position: "top" }); - } - }; - - // 搜索防抖处理 - const [searchInput, setSearchInput] = useState(search); - - const debouncedSearch = useCallback(() => { - const timer = setTimeout(() => { - setSearch(searchInput); - // 搜索时重置到第一页并请求列表 - setPage(1); - getList({ keyword: searchInput, page: 1 }); - }, 500); // 500ms 防抖延迟 - - return () => clearTimeout(timer); - }, [searchInput]); - - useEffect(() => { - const cleanup = debouncedSearch(); - return cleanup; - }, [debouncedSearch]); - - const handSearch = (value: string) => { - setSearchInput(value); - setSelectedIds([]); - debouncedSearch(); - }; + fetchData(); + }, [page, pageSize, search]); return ( - setShowStats(s => !s)} - style={{ marginLeft: 8 }} - > - {showStats ? "收起分析" : "数据分析"} - - } - /> - {/* 搜索栏 */} +
handSearch(e.target.value)} + placeholder="搜索用户" + value={search} + onChange={e => handleSearch(e.target.value)} prefix={} allowClear size="large" />
-
- {/* 数据分析面板 */} - { - // 可以在这里处理统计数据,比如更新本地状态或发送到父组件 - console.log("收到统计数据:", statsData); - }} - /> - - {/* 批量操作栏 */} -
- 0} - onChange={e => handleSelectAll(e.target.checked)} - style={{ marginRight: 8 }} /> - 全选 - {selectedIds.length > 0 && ( - <> - {`已选${selectedIds.length}项`} - - - )} - {searchInput.length > 0 && ( - <> - - - )} -
-
} @@ -283,56 +79,14 @@ const TrafficPoolList: React.FC = () => {
{ - setPage(newPage); - getList({ page: newPage }); - }} + onChange={setPage} />
} > - {/* 批量加入分组弹窗 */} - setBatchModal(false)} - selectedCount={selectedIds.length} - onConfirm={data => { - // 处理批量加入逻辑 - handleBatchAdd(data); - }} - /> - {/* 筛选弹窗 */} - setShowFilter(false)} - onConfirm={filters => { - // 更新公共筛选条件状态 - const newFilterParams = { - selectedDevices: filters.selectedDevices, - packageId: filters.packageld, - scenarioId: filters.sceneId, - userValue: filters.userValue, - userStatus: filters.addStatus, - }; - - setFilterParams(newFilterParams); - // 重置到第一页并请求列表 - setPage(1); - getList({ - page: 1, - packageld: newFilterParams.packageId, - sceneId: newFilterParams.scenarioId, - userValue: newFilterParams.userValue, - addStatus: newFilterParams.userStatus, - deviceld: newFilterParams.selectedDevices.map(d => d.id).join(), - }); - }} - scenarioOptions={scenarioOptions} - initialFilters={filterParams} - />
{list.length === 0 && !loading ? ( @@ -350,13 +104,6 @@ const TrafficPoolList: React.FC = () => { } >
- handleSelect(item.id, e.target.checked)} - style={{ marginRight: 8 }} - onClick={e => e.stopPropagation()} - className={styles.checkbox} - /> {
{item.nickname || item.identifier} - {/* 性别icon可自行封装 */}
微信号:{item.wechatId || "-"} diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/BatchAddModal.tsx similarity index 100% rename from Cunkebao/src/pages/mobile/mine/traffic-pool/list/BatchAddModal.tsx rename to Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/BatchAddModal.tsx diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/DataAnalysisPanel.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/DataAnalysisPanel.tsx similarity index 100% rename from Cunkebao/src/pages/mobile/mine/traffic-pool/list/DataAnalysisPanel.tsx rename to Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/DataAnalysisPanel.tsx diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/FilterModal.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/FilterModal.tsx similarity index 100% rename from Cunkebao/src/pages/mobile/mine/traffic-pool/list/FilterModal.tsx rename to Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/FilterModal.tsx diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/api.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/api.ts new file mode 100644 index 00000000..9b53c030 --- /dev/null +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/api.ts @@ -0,0 +1,34 @@ +import request from "@/api/request"; + +// 获取流量池列表 +export function fetchTrafficPoolList(params: { + page?: number; + pageSize?: number; + keyword?: string; +}) { + return request("/v1/traffic/pool", params, "GET"); +} + +export async function fetchScenarioOptions() { + return request("/v1/plan/scenes", {}, "GET"); +} + +export async function fetchPackageOptions() { + return request("/v1/traffic/pool/getPackage", {}, "GET"); +} + +export async function addPackage(params: { + type: string; // 类型 1搜索 2选择用户 3文件上传 + addPackageId?: number; + addStatus?: number; + deviceId?: string; + keyword?: string; + packageId?: number; + packageName?: number; // 添加的流量池名称 + tableFile?: number; + taskId?: number; // 任务id j及场景获客id + userIds?: number[]; + userValue?: number; +}) { + return request("/v1/traffic/pool/addPackage", params, "POST"); +} diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/data.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/data.ts new file mode 100644 index 00000000..65ad7f55 --- /dev/null +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/data.ts @@ -0,0 +1,51 @@ +// 流量池用户类型 +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"; + +// 获客场景类型 +export interface ScenarioOption { + id: string; + name: string; +} diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/index.module.scss b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/index.module.scss new file mode 100644 index 00000000..2fbee5e3 --- /dev/null +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/index.module.scss @@ -0,0 +1,65 @@ +.listWrap { + padding: 12px; +} + +.cardContent { + display: flex; + align-items: center; + gap: 12px; + position: relative; +} +.checkbox { + position: absolute; + top: 0; + left: 0; +} +.cardWrap { + background: #fff; + padding: 16px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + margin-bottom: 12px; +} + +.card { + 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/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/index.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/index.tsx new file mode 100644 index 00000000..c5c6306d --- /dev/null +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/poolList/index.tsx @@ -0,0 +1,396 @@ +import React, { useCallback, useEffect, useState } from "react"; +import Layout from "@/components/Layout/Layout"; +import { + SearchOutlined, + ReloadOutlined, + BarChartOutlined, +} from "@ant-design/icons"; +import { Toast } from "antd-mobile"; +import { Input, Button, Checkbox, Pagination } from "antd"; +import styles from "./index.module.scss"; +import { Empty, Avatar } from "antd-mobile"; +import { useNavigate } from "react-router-dom"; +import NavCommon from "@/components/NavCommon"; +import { fetchTrafficPoolList, fetchScenarioOptions, addPackage } from "./api"; +import type { TrafficPoolUser, ScenarioOption } from "./data"; +import DataAnalysisPanel from "./DataAnalysisPanel"; +import FilterModal from "./FilterModal"; +import BatchAddModal from "./BatchAddModal"; +import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; +const defaultAvatar = + "https://cdn.jsdelivr.net/gh/maokaka/static/avatar-default.png"; + +const TrafficPoolList: React.FC = () => { + const navigate = useNavigate(); + + // 基础状态 + 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 [scenarioOptions, setScenarioOptions] = useState([]); + + // 公共筛选条件状态 + const [filterParams, setFilterParams] = useState({ + selectedDevices: [] as DeviceSelectionItem[], + packageId: 0, + scenarioId: 0, + userValue: 0, + userStatus: 0, + }); + + // 批量相关 + const [selectedIds, setSelectedIds] = useState([]); + const [batchModal, setBatchModal] = useState(false); + + // 数据分析 + const [showStats, setShowStats] = useState(false); + + // 获取列表 + const getList = async (customParams?: any) => { + setLoading(true); + try { + const params: any = { + page, + pageSize, + keyword: search, + packageld: filterParams.packageId, + sceneId: filterParams.scenarioId, + userValue: filterParams.userValue, + addStatus: filterParams.userStatus, + deviceld: filterParams.selectedDevices.map(d => d.id).join(), + ...customParams, // 允许传入自定义参数覆盖 + }; + + const res = await fetchTrafficPoolList(params); + setList(res.list || []); + setTotal(res.total || 0); + } catch (error) { + // 忽略请求过于频繁的错误,避免页面崩溃 + if (error !== "请求过于频繁,请稍后再试") { + console.error("获取列表失败:", error); + } + } finally { + setLoading(false); + } + }; + + // 获取筛选项 + useEffect(() => { + fetchScenarioOptions().then(res => { + setScenarioOptions(res.list || []); + }); + }, []); + + // 全选/反选 + 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 = async options => { + try { + // 构建请求参数 + const params = { + type: "2", // 2选择用户 + addPackageId: options.selectedPackageId, // 目标分组ID + userIds: selectedIds.map(id => id), // 选中的用户ID数组 + // 如果有当前筛选条件,也可以传递 + ...(filterParams.packageId && { + packageId: filterParams.packageId, + }), + ...(filterParams.scenarioId && { + taskId: filterParams.scenarioId, + }), + ...(filterParams.userValue && { + userValue: filterParams.userValue, + }), + ...(filterParams.userStatus && { + addStatus: filterParams.userStatus, + }), + ...(filterParams.selectedDevices.length > 0 && { + deviceId: filterParams.selectedDevices.map(d => d.id).join(","), + }), + ...(search && { keyword: search }), + }; + + console.log("批量加入请求参数:", params); + + // 调用接口 + const result = await addPackage(params); + console.log("批量加入结果:", result); + + // 成功后刷新列表 + getList(); + + // 关闭弹窗并清空选择 + setBatchModal(false); + setSelectedIds([]); + + // 可以添加成功提示 + Toast.show({ + content: `成功将用户加入分组`, + position: "top", + }); + } catch (error) { + console.error("批量加入失败:", error); + // 可以添加错误提示 + Toast.show({ content: "批量加入失败,请重试", position: "top" }); + } + }; + + // 搜索防抖处理 + const [searchInput, setSearchInput] = useState(search); + + const debouncedSearch = useCallback(() => { + const timer = setTimeout(() => { + setSearch(searchInput); + // 搜索时重置到第一页并请求列表 + setPage(1); + getList({ keyword: searchInput, page: 1 }); + }, 500); // 500ms 防抖延迟 + + return () => clearTimeout(timer); + }, [searchInput]); + + useEffect(() => { + const cleanup = debouncedSearch(); + return cleanup; + }, [debouncedSearch]); + + const handSearch = (value: string) => { + setSearchInput(value); + setSelectedIds([]); + debouncedSearch(); + }; + + return ( + + setShowStats(s => !s)} + style={{ marginLeft: 8 }} + > + {showStats ? "收起分析" : "数据分析"} + + } + /> + {/* 搜索栏 */} +
+
+ handSearch(e.target.value)} + prefix={} + allowClear + size="large" + /> +
+ +
+ {/* 数据分析面板 */} + { + // 可以在这里处理统计数据,比如更新本地状态或发送到父组件 + console.log("收到统计数据:", statsData); + }} + /> + + {/* 批量操作栏 */} +
+ 0} + onChange={e => handleSelectAll(e.target.checked)} + style={{ marginRight: 8 }} + /> + 全选 + {selectedIds.length > 0 && ( + <> + {`已选${selectedIds.length}项`} + + + )} + {searchInput.length > 0 && ( + <> + + + )} +
+ +
+ + } + footer={ +
+ { + setPage(newPage); + getList({ page: newPage }); + }} + /> +
+ } + > + {/* 批量加入分组弹窗 */} + setBatchModal(false)} + selectedCount={selectedIds.length} + onConfirm={data => { + // 处理批量加入逻辑 + handleBatchAdd(data); + }} + /> + {/* 筛选弹窗 */} + setShowFilter(false)} + onConfirm={filters => { + // 更新公共筛选条件状态 + const newFilterParams = { + selectedDevices: filters.selectedDevices, + packageId: filters.packageld, + scenarioId: filters.sceneId, + userValue: filters.userValue, + userStatus: filters.addStatus, + }; + + setFilterParams(newFilterParams); + // 重置到第一页并请求列表 + setPage(1); + getList({ + page: 1, + packageld: newFilterParams.packageId, + sceneId: newFilterParams.scenarioId, + userValue: newFilterParams.userValue, + addStatus: newFilterParams.userStatus, + deviceld: newFilterParams.selectedDevices.map(d => d.id).join(), + }); + }} + scenarioOptions={scenarioOptions} + initialFilters={filterParams} + /> +
+ {list.length === 0 && !loading ? ( + + ) : ( +
+ {list.map(item => ( +
+
+ navigate( + `/mine/traffic-pool/detail/${item.wechatId}/${item.id}`, + ) + } + > +
+ handleSelect(item.id, e.target.checked)} + style={{ marginRight: 8 }} + onClick={e => e.stopPropagation()} + className={styles.checkbox} + /> + +
+
+ {item.nickname || item.identifier} + {/* 性别icon可自行封装 */} +
+
+ 微信号:{item.wechatId || "-"} +
+
+ 来源:{item.fromd || "-"} +
+
+ 分组: + {item.packages && item.packages.length + ? item.packages.join(",") + : "-"} +
+
+ 创建时间:{item.createTime} +
+
+
+
+
+ ))} +
+ )} +
+ + ); +}; + +export default TrafficPoolList; diff --git a/Cunkebao/src/router/module/mine.tsx b/Cunkebao/src/router/module/mine.tsx index 0017d157..e7038ddd 100644 --- a/Cunkebao/src/router/module/mine.tsx +++ b/Cunkebao/src/router/module/mine.tsx @@ -2,6 +2,7 @@ import Mine from "@/pages/mobile/mine/main/index"; import Devices from "@/pages/mobile/mine/devices/index"; import DeviceDetail from "@/pages/mobile/mine/devices/DeviceDetail"; import TrafficPool from "@/pages/mobile/mine/traffic-pool/list/index"; +import TrafficPoolList from "@/pages/mobile/mine/traffic-pool/poolList/index"; import TrafficPoolDetail from "@/pages/mobile/mine/traffic-pool/detail/index"; import WechatAccounts from "@/pages/mobile/mine/wechat-accounts/list/index"; import WechatAccountDetail from "@/pages/mobile/mine/wechat-accounts/detail/index"; @@ -34,6 +35,11 @@ const routes = [ element: , auth: true, }, + { + path: "/mine/traffic-pool/list", + element: , + auth: true, + }, { path: "/mine/traffic-pool/detail/:wxid/:userId", element: ,