diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 5c1e7050..70d5d6b2 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -1,9 +1,9 @@ { - "_charts-TuAbbBZ5.js": { - "file": "assets/charts-TuAbbBZ5.js", + "_charts-D0fT04H8.js": { + "file": "assets/charts-D0fT04H8.js", "name": "charts", "imports": [ - "_ui-D1w-jetn.js", + "_ui-qLeQLv1F.js", "_vendor-2vc8h_ct.js" ] }, @@ -11,8 +11,8 @@ "file": "assets/ui-D0C0OGrH.css", "src": "_ui-D0C0OGrH.css" }, - "_ui-D1w-jetn.js": { - "file": "assets/ui-D1w-jetn.js", + "_ui-qLeQLv1F.js": { + "file": "assets/ui-qLeQLv1F.js", "name": "ui", "imports": [ "_vendor-2vc8h_ct.js" @@ -33,18 +33,18 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-DTZ_ow5W.js", + "file": "assets/index-Cp05akVy.js", "name": "index", "src": "index.html", "isEntry": true, "imports": [ "_vendor-2vc8h_ct.js", - "_ui-D1w-jetn.js", + "_ui-qLeQLv1F.js", "_utils-6WF66_dS.js", - "_charts-TuAbbBZ5.js" + "_charts-D0fT04H8.js" ], "css": [ - "assets/index-B0SB167P.css" + "assets/index-Eg_DAu9e.css" ] } } \ No newline at end of file diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index a3b55d54..6261e31e 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -10,14 +10,14 @@ } - - + + - + - + - +
diff --git a/Cunkebao/index.html b/Cunkebao/index.html index 92ab92a7..16de6c67 100644 --- a/Cunkebao/index.html +++ b/Cunkebao/index.html @@ -10,7 +10,7 @@ } - +
diff --git a/Cunkebao/src/components/DeviceSelection/index.tsx b/Cunkebao/src/components/DeviceSelection/index.tsx index 997c56f2..ba6952cd 100644 --- a/Cunkebao/src/components/DeviceSelection/index.tsx +++ b/Cunkebao/src/components/DeviceSelection/index.tsx @@ -46,6 +46,12 @@ const DeviceSelection: React.FC = ({ onSelect(selectedOptions.filter(v => v.id !== id)); }; + // 清除所有已选设备 + const handleClearAll = () => { + if (readonly) return; + onSelect([]); + }; + return ( <> {/* mode=input 显示输入框,mode=dialog不显示 */} @@ -57,6 +63,7 @@ const DeviceSelection: React.FC = ({ onClick={openPopup} prefix={} allowClear={!readonly} + onClear={handleClearAll} size="large" readOnly={readonly} disabled={readonly} diff --git a/Cunkebao/src/pages/mobile/mine/main/index.tsx b/Cunkebao/src/pages/mobile/mine/main/index.tsx index b3d44912..b6fd294f 100644 --- a/Cunkebao/src/pages/mobile/mine/main/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/main/index.tsx @@ -64,7 +64,7 @@ const Mine: React.FC = () => { description: "管理用户流量池和分组", icon: , count: stats.traffic, - path: "/traffic-pool", + path: "/mine/traffic-pool", bgColor: "#f9f0ff", iconColor: "#722ed1", }, diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/FilterModal.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/FilterModal.tsx index b36dedb0..baeb21c1 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/FilterModal.tsx +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/FilterModal.tsx @@ -1,27 +1,28 @@ -import React from "react"; +import React, { useState } from "react"; import { Popup } from "antd-mobile"; import { Select, Button } from "antd"; +import DeviceSelection from "@/components/DeviceSelection"; import type { - DeviceOption, PackageOption, ValueLevel, UserStatus, + ScenarioOption, } from "./data"; +import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; + interface FilterModalProps { visible: boolean; onClose: () => void; - deviceOptions: DeviceOption[]; + onConfirm: (filters: { + deviceIds: string[]; + packageId: string; + scenarioId: string; + valueLevel: ValueLevel; + userStatus: UserStatus; + }) => void; packageOptions: PackageOption[]; - deviceId: string; - setDeviceId: (v: string) => void; - packageId: string; - setPackageId: (v: string) => void; - valueLevel: ValueLevel; - setValueLevel: (v: ValueLevel) => void; - userStatus: UserStatus; - setUserStatus: (v: UserStatus) => void; - onReset: () => void; + scenarioOptions: ScenarioOption[]; } const valueLevelOptions = [ @@ -41,78 +42,109 @@ const statusOptions = [ const FilterModal: React.FC = ({ visible, onClose, - deviceOptions, packageOptions, - deviceId, - setDeviceId, - packageId, - setPackageId, - valueLevel, - setValueLevel, - userStatus, - setUserStatus, - onReset, -}) => ( - -
- 筛选选项 -
-
-
设备
- ({ label: p.name, value: p.id })), - ]} - /> -
-
-
用户价值
- setUserStatus(v as UserStatus)} - options={statusOptions} - /> -
-
- - -
-
-); + scenarioOptions, + onConfirm, +}) => { + const [selectedDevices, setSelectedDevices] = useState( + [], + ); + const [packageId, setPackageId] = useState("all"); + const [scenarioId, setScenarioId] = useState("all"); + const [valueLevel, setValueLevel] = useState("all"); + const [userStatus, setUserStatus] = useState("all"); + + const handleApply = () => { + onConfirm({ + deviceIds: selectedDevices.map(d => d.id.toString()), + packageId, + scenarioId, + valueLevel, + userStatus, + }); + onClose(); + }; + + const handleReset = () => { + setSelectedDevices([]); + setPackageId("all"); + setScenarioId("all"); + setValueLevel("all"); + setUserStatus("all"); + }; + + return ( + +
+ 筛选选项 +
+
+
设备
+ +
+
+
流量池
+ ({ label: s.name, value: s.id })), + ]} + /> +
+
+
用户价值
+ setUserStatus(v as UserStatus)} + options={statusOptions} + /> +
+
+ + +
+
+ ); +}; export default FilterModal; diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts index 0a6162a8..de5bfff1 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/api.ts @@ -17,3 +17,14 @@ export async function fetchPackageOptions(): Promise { { id: "pkg-2", name: "测试流量池" }, ]; } + +// 获取获客场景列表 +export async function fetchScenarioOptions(): Promise { + // TODO: 替换为真实接口 + return [ + { id: "scenario-1", name: "朋友圈推广" }, + { id: "scenario-2", name: "群聊引流" }, + { id: "scenario-3", name: "公众号推广" }, + { id: "scenario-4", name: "线下活动" }, + ]; +} diff --git a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/data.ts b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/data.ts index f437b129..65ad7f55 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/data.ts +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/data.ts @@ -43,3 +43,9 @@ 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/list/dataAnyx.tsx b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx index 7a84040a..b0d3f8ee 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx @@ -1,11 +1,16 @@ import { useState, useEffect, useMemo } from "react"; -import { fetchTrafficPoolList, fetchPackageOptions } from "./api"; +import { + fetchTrafficPoolList, + fetchPackageOptions, + fetchScenarioOptions, +} from "./api"; import type { TrafficPoolUser, DeviceOption, PackageOption, ValueLevel, UserStatus, + ScenarioOption, } from "./data"; import { Toast } from "antd-mobile"; @@ -19,10 +24,11 @@ export function useTrafficPoolListLogic() { // 筛选相关 const [showFilter, setShowFilter] = useState(false); - const [deviceOptions, setDeviceOptions] = useState([]); const [packageOptions, setPackageOptions] = useState([]); - const [deviceId, setDeviceId] = useState("all"); + const [scenarioOptions, setScenarioOptions] = useState([]); + const [selectedDevices, setSelectedDevices] = useState([]); const [packageId, setPackageId] = useState("all"); + const [scenarioId, setScenarioId] = useState("all"); const [valueLevel, setValueLevel] = useState("all"); const [userStatus, setUserStatus] = useState("all"); @@ -47,15 +53,38 @@ export function useTrafficPoolListLogic() { const getList = async () => { setLoading(true); try { - const res = await fetchTrafficPoolList({ + const params: any = { page, pageSize, keyword: search, - // deviceId, - // packageId, - // valueLevel, - // userStatus, - }); + }; + + // 添加筛选参数 + if (selectedDevices.length > 0) { + params.deviceId = selectedDevices.map(d => d.id).join(","); + } + if (packageId !== "all") { + params.packageId = packageId; + } + if (scenarioId !== "all") { + params.taskId = scenarioId; + } + if (valueLevel !== "all") { + params.userValue = + valueLevel === "high" ? 3 : valueLevel === "medium" ? 2 : 1; + } + if (userStatus !== "all") { + params.addStatus = + userStatus === "added" + ? 1 + : userStatus === "pending" + ? 0 + : userStatus === "failed" + ? -1 + : -2; + } + + const res = await fetchTrafficPoolList(params); setList(res.list || []); setTotal(res.total || 0); } finally { @@ -66,13 +95,22 @@ export function useTrafficPoolListLogic() { // 获取筛选项 useEffect(() => { fetchPackageOptions().then(setPackageOptions); + fetchScenarioOptions().then(setScenarioOptions); }, []); // 筛选条件变化时刷新列表 useEffect(() => { getList(); // eslint-disable-next-line - }, [page, search /*, deviceId, packageId, valueLevel, userStatus*/]); + }, [ + page, + search, + selectedDevices, + packageId, + scenarioId, + valueLevel, + userStatus, + ]); // 全选/反选 const handleSelectAll = (checked: boolean) => { @@ -108,8 +146,9 @@ export function useTrafficPoolListLogic() { // 筛选重置 const resetFilter = () => { - setDeviceId("all"); + setSelectedDevices([]); setPackageId("all"); + setScenarioId("all"); setValueLevel("all"); setUserStatus("all"); }; @@ -125,12 +164,14 @@ export function useTrafficPoolListLogic() { setSearch, showFilter, setShowFilter, - deviceOptions, packageOptions, - deviceId, - setDeviceId, + scenarioOptions, + selectedDevices, + setSelectedDevices, packageId, setPackageId, + scenarioId, + setScenarioId, valueLevel, setValueLevel, userStatus, 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 7e000524..4702737d 100644 --- a/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/traffic-pool/list/index.tsx @@ -7,7 +7,7 @@ import { } from "@ant-design/icons"; import { Input, Button, Checkbox } from "antd"; import styles from "./index.module.scss"; -import { List, Empty, Avatar, Modal, Selector, Toast, Card } from "antd-mobile"; +import { Empty, Avatar } from "antd-mobile"; import { useNavigate } from "react-router-dom"; import NavCommon from "@/components/NavCommon"; import { useTrafficPoolListLogic } from "./dataAnyx"; @@ -18,20 +18,6 @@ import BatchAddModal from "./BatchAddModal"; 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 navigate = useNavigate(); const { @@ -45,12 +31,14 @@ const TrafficPoolList: React.FC = () => { setSearch, showFilter, setShowFilter, - deviceOptions, packageOptions, - deviceId, - setDeviceId, + scenarioOptions, + selectedDevices, + setSelectedDevices, packageId, setPackageId, + scenarioId, + setScenarioId, valueLevel, setValueLevel, userStatus, @@ -169,17 +157,26 @@ const TrafficPoolList: React.FC = () => { setShowFilter(false)} - deviceOptions={deviceOptions} + onConfirm={filters => { + // 更新筛选条件 + setSelectedDevices( + filters.deviceIds.map(id => ({ + id: parseInt(id), + memo: "", + imei: "", + wechatId: "", + status: "offline" as const, + })), + ); + setPackageId(filters.packageId); + setScenarioId(filters.scenarioId); + setValueLevel(filters.valueLevel); + setUserStatus(filters.userStatus); + // 重新获取列表 + getList(); + }} packageOptions={packageOptions} - deviceId={deviceId} - setDeviceId={setDeviceId} - packageId={packageId} - setPackageId={setPackageId} - valueLevel={valueLevel} - setValueLevel={setValueLevel} - userStatus={userStatus} - setUserStatus={setUserStatus} - onReset={resetFilter} + scenarioOptions={scenarioOptions} />
{list.length === 0 && !loading ? ( @@ -192,7 +189,9 @@ const TrafficPoolList: React.FC = () => { className={styles.card} style={{ cursor: "pointer" }} onClick={() => - navigate(`/traffic-pool/detail/${item.sourceId}/${item.id}`) + navigate( + `/mine/traffic-pool/detail/${item.sourceId}/${item.id}`, + ) } >
diff --git a/Cunkebao/src/router/module/mine.tsx b/Cunkebao/src/router/module/mine.tsx index e41f455d..e5c33623 100644 --- a/Cunkebao/src/router/module/mine.tsx +++ b/Cunkebao/src/router/module/mine.tsx @@ -36,12 +36,12 @@ const routes = [ auth: true, }, { - path: "/traffic-pool", + path: "/mine/traffic-pool", element: , auth: true, }, { - path: "/traffic-pool/detail/:wxid/:userId", + path: "/mine/traffic-pool/detail/:wxid/:userId", element: , auth: true, },