From 9cd285223b66c4b6f789907ba839f5c9c94f76f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 15 Aug 2025 17:42:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E6=A2=9D=E7=B6=A0?= =?UTF-8?q?=E8=89=B2=E5=B9=B3=E6=BB=91=E7=B7=9A=E5=9C=96=E6=95=B8=E6=93=9A?= =?UTF-8?q?=EF=BC=8C=E4=B8=A6=E8=AA=BF=E6=95=B4=E7=9B=B8=E6=87=89=E7=9A=84?= =?UTF-8?q?=E6=A8=A3=E5=BC=8F=E5=B1=AC=E6=80=A7=E4=BB=A5=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E5=8F=AF=E8=A6=96=E5=8C=96=E6=95=88=E6=9E=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/public/iOS_WebClip.mobileconfig | 68 -- Cunkebao/src/components/LineChart2.tsx | 57 ++ .../pages/mobile/scenarios/plan/list/api.ts | 9 + .../plan/list/components/AccountListModal.tsx | 175 ++++ .../plan/list/components/DeviceListModal.tsx | 175 ++++ .../plan/list/components/OreadyAdd.tsx | 175 ++++ .../plan/list/components/PoolListModal.tsx | 161 ++++ .../plan/list/components/Popups.module.scss | 744 ++++++++++++++++++ .../pages/mobile/scenarios/plan/list/data.ts | 1 + .../scenarios/plan/list/index.module.scss | 19 + .../mobile/scenarios/plan/list/index.tsx | 114 ++- Cunkebao/src/utils/chartColors.ts | 67 ++ 12 files changed, 1681 insertions(+), 84 deletions(-) delete mode 100644 Cunkebao/public/iOS_WebClip.mobileconfig create mode 100644 Cunkebao/src/components/LineChart2.tsx create mode 100644 Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx create mode 100644 Cunkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx create mode 100644 Cunkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx create mode 100644 Cunkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx create mode 100644 Cunkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss create mode 100644 Cunkebao/src/utils/chartColors.ts diff --git a/Cunkebao/public/iOS_WebClip.mobileconfig b/Cunkebao/public/iOS_WebClip.mobileconfig deleted file mode 100644 index 0f502185..00000000 --- a/Cunkebao/public/iOS_WebClip.mobileconfig +++ /dev/null @@ -1,68 +0,0 @@ - - - - - ConsentText - - default - 请点击右上角「下一步」按钮↗️ - 如果需要输入密码,请输入锁屏密码继续安装。 - - 第一次使用需要加载比较长时间,请耐心等待。 - 轻量版永不掉签,仅是在手机桌面添加一个平台入口。该安装证书已通过苹果官方认证,安全可靠,不会修改任何手机设置,请放心安装使用。 - - 永久地址为:https://m.xmbaiqi.com - - - PayloadContent - - - FullScreen - - Icon - - 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 - - IsRemovable - - Label - 存客宝 - PayloadDescription - 配置 Web Clip 设置 - PayloadDisplayName - Web Clip - PayloadIdentifier - com.apple.webClip.managed.46594155-65DD-1564-41DC-35EGEWGRGRW - PayloadType - com.apple.webClip.managed - PayloadUUID - 46594155-65DD-1564-41DC-35EGEWGRGRW - PayloadVersion - 1 - Precomposed - - URL - https://m.xmbaiqi.com - - - PayloadDescription - 请点击右上角「安装」按钮↗️ - 如果需要输入密码,请输入锁屏密码继续安装。 - 第一次使用需要加载比较长时间,请耐心等待。 - 轻量版永不掉签,仅是在手机桌面添加一个平台入口。该安装证书已通过苹果官方认证,安全可靠,不会修改任何手机设置,请放心安装使用。 - WePoker永久地址https://m.xmbaiqi.com - - PayloadDisplayName - WePoker - PayloadIdentifier - iMac-Pro.5DF561DF5D-2D6D-24GE-2E7H-5GE7G5REG - PayloadRemovalDisallowed - - PayloadType - Configuration - PayloadUUID - DF561DF5D-2D6D-24GE-2E7H-5GE7G5REG - PayloadVersion - 2 - - diff --git a/Cunkebao/src/components/LineChart2.tsx b/Cunkebao/src/components/LineChart2.tsx new file mode 100644 index 00000000..2a6f401e --- /dev/null +++ b/Cunkebao/src/components/LineChart2.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import ReactECharts from "echarts-for-react"; +import { getChartColor } from "@/utils/chartColors"; + +interface LineChartProps { + title?: string; + xData: string[]; + yData: any[]; + height?: number | string; +} + +const LineChart: React.FC = ({ + title = "", + xData, + yData, + height = 200, +}) => { + const option = { + title: { + text: title, + left: "center", + textStyle: { fontSize: 16 }, + }, + tooltip: { trigger: "axis" }, + xAxis: { + type: "category", + data: xData, + boundaryGap: false, + }, + yAxis: { + type: "value", + boundaryGap: ["10%", "10%"], // 上下留白 + min: (value: any) => value.min - 10, // 下方多留一点空间 + max: (value: any) => value.max + 10, // 上方多留一点空间 + minInterval: 1, + axisLabel: { margin: 12 }, + }, + series: [ + ...yData.map((item, index) => { + const color = getChartColor(index); + return { + data: item, + type: "line", + smooth: true, + symbol: "circle", + lineStyle: { color }, + itemStyle: { color }, + }; + }), + ], + grid: { left: 40, right: 24, top: 40, bottom: 32 }, + }; + + return ; +}; + +export default LineChart; diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/api.ts b/Cunkebao/src/pages/mobile/scenarios/plan/list/api.ts index 8562ee44..26377d6c 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/list/api.ts +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/api.ts @@ -30,3 +30,12 @@ export function deletePlan(planId: string): Promise> { export function getWxMinAppCode(planId: string): Promise> { return request(`/v1/plan/getWxMinAppCode`, { taskId: planId }, "GET"); } +//获客列表 +export function getUserList(planId: string, type: number) { + return request(`/v1/plan/getUserList`, { planId, type }, "GET"); +} + +//获客列表 +export function getFriendRequestTaskStats(taskId: string) { + return request(`/v1/dashboard/friendRequestTaskStats`, { taskId }, "GET"); +} diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx new file mode 100644 index 00000000..dd8cff30 --- /dev/null +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/AccountListModal.tsx @@ -0,0 +1,175 @@ +import React, { useEffect, useState } from "react"; +import { Popup, Avatar, SpinLoading } from "antd-mobile"; +import { Button, message } from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import style from "./Popups.module.scss"; +import { getUserList } from "../api"; + +interface AccountItem { + id: string | number; + nickname?: string; + wechatId?: string; + avatar?: string; + status?: string; + userinfo: { + alias: string; + nickname: string; + avatar: string; + wechatId: string; + }; + phone?: string; +} + +interface AccountListModalProps { + visible: boolean; + onClose: () => void; + ruleId?: number; + ruleName?: string; +} + +const AccountListModal: React.FC = ({ + visible, + onClose, + ruleId, + ruleName, +}) => { + const [accounts, setAccounts] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取账号数据 + const fetchAccounts = async () => { + if (!ruleId) return; + + setLoading(true); + try { + const detailRes = await getUserList(ruleId.toString(), 1); + const accountData = detailRes?.list || []; + setAccounts(accountData); + } catch (error) { + console.error("获取账号详情失败:", error); + message.error("获取账号详情失败"); + } finally { + setLoading(false); + } + }; + + // 当弹窗打开且有ruleId时,获取数据 + useEffect(() => { + if (visible && ruleId) { + fetchAccounts(); + } + }, [visible, ruleId]); + + const title = ruleName ? `${ruleName} - 已添加账号列表` : "已添加账号列表"; + const getStatusColor = (status?: string) => { + switch (status) { + case "normal": + return "#52c41a"; + case "limited": + return "#faad14"; + case "blocked": + return "#ff4d4f"; + default: + return "#d9d9d9"; + } + }; + + const getStatusText = (status?: string) => { + switch (status) { + case "normal": + return "正常"; + case "limited": + return "受限"; + case "blocked": + return "封禁"; + default: + return "未知"; + } + }; + + return ( + +
+ {/* 头部 */} +
+

{title}

+
+ + {/* 账号列表 */} +
+ {loading ? ( +
+ +
+ 正在加载账号列表... +
+
+ ) : accounts.length > 0 ? ( + accounts.map((account, index) => ( +
+
+ +
+
+
+ {account.userinfo.nickname || + account.userinfo.alias || + `账号${account.id}`} +
+
+ {account.userinfo.wechatId || "未绑定微信号"} +
+
+
+ + + {getStatusText(account.status)} + +
+
+ )) + ) : ( +
+
暂无账号数据
+
+ )} +
+ + {/* 底部统计 */} +
+
+ 共 {accounts.length} 个账号 +
+
+
+
+ ); +}; + +export default AccountListModal; diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx new file mode 100644 index 00000000..111483cd --- /dev/null +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/DeviceListModal.tsx @@ -0,0 +1,175 @@ +import React, { useEffect, useState } from "react"; +import { Popup, Avatar, SpinLoading } from "antd-mobile"; +import { Button, message } from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import style from "./Popups.module.scss"; +import { getPlanDetail } from "../api"; + +interface DeviceItem { + id: string | number; + memo?: string; + imei?: string; + wechatId?: string; + status?: "online" | "offline"; + avatar?: string; + totalFriend?: number; +} + +interface DeviceListModalProps { + visible: boolean; + onClose: () => void; + ruleId?: number; + ruleName?: string; +} + +const DeviceListModal: React.FC = ({ + visible, + onClose, + ruleId, + ruleName, +}) => { + const [devices, setDevices] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取设备数据 + const fetchDevices = async () => { + if (!ruleId) return; + + setLoading(true); + try { + const detailRes = await getPlanDetail(ruleId.toString()); + const deviceData = detailRes?.deveiceGroupsOptions || []; + setDevices(deviceData); + } catch (error) { + console.error("获取设备详情失败:", error); + message.error("获取设备详情失败"); + } finally { + setLoading(false); + } + }; + + // 当弹窗打开且有ruleId时,获取数据 + useEffect(() => { + if (visible && ruleId) { + fetchDevices(); + } + }, [visible, ruleId]); + + const title = ruleName ? `${ruleName} - 分发设备列表` : "分发设备列表"; + const getStatusColor = (status?: string) => { + return status === "online" ? "#52c41a" : "#ff4d4f"; + }; + + const getStatusText = (status?: string) => { + return status === "online" ? "在线" : "离线"; + }; + + return ( + +
+ {/* 头部 */} +
+

{title}

+
+ + {/* 设备列表 */} +
+ {loading ? ( +
+ +
正在加载设备列表...
+
+ ) : devices.length > 0 ? ( + devices.map((device, index) => ( +
+ {/* 顶部行:IMEI */} +
+ + IMEI: {device.imei?.toUpperCase() || "-"} + +
+ + {/* 主要内容区域:头像和详细信息 */} +
+ {/* 头像 */} +
+ {device.avatar ? ( + 头像 + ) : ( + + {(device.memo || device.wechatId || "设")[0]} + + )} +
+ + {/* 设备信息 */} +
+
+

+ {device.memo || "未命名设备"} +

+ + {getStatusText(device.status)} + +
+ +
+
+ 微信号: + + {device.wechatId || "未绑定"} + +
+
+ 好友数: + + {device.totalFriend ?? "-"} + +
+
+
+
+
+ )) + ) : ( +
+
暂无设备数据
+
+ )} +
+ + {/* 底部统计 */} +
+
+ 共 {devices.length} 个设备 +
+
+
+
+ ); +}; + +export default DeviceListModal; diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx new file mode 100644 index 00000000..fa6e1853 --- /dev/null +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/OreadyAdd.tsx @@ -0,0 +1,175 @@ +import React, { useEffect, useState } from "react"; +import { Popup, Avatar, SpinLoading } from "antd-mobile"; +import { Button, message } from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import style from "./Popups.module.scss"; +import { getUserList } from "../api"; + +interface AccountItem { + id: string | number; + nickname?: string; + wechatId?: string; + avatar?: string; + status?: string; + userinfo: { + alias: string; + nickname: string; + avatar: string; + wechatId: string; + }; + phone?: string; +} + +interface AccountListModalProps { + visible: boolean; + onClose: () => void; + ruleId?: number; + ruleName?: string; +} + +const AccountListModal: React.FC = ({ + visible, + onClose, + ruleId, + ruleName, +}) => { + const [accounts, setAccounts] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取账号数据 + const fetchAccounts = async () => { + if (!ruleId) return; + + setLoading(true); + try { + const detailRes = await getUserList(ruleId.toString(), 2); + const accountData = detailRes?.list || []; + setAccounts(accountData); + } catch (error) { + console.error("获取账号详情失败:", error); + message.error("获取账号详情失败"); + } finally { + setLoading(false); + } + }; + + // 当弹窗打开且有ruleId时,获取数据 + useEffect(() => { + if (visible && ruleId) { + fetchAccounts(); + } + }, [visible, ruleId]); + + const title = ruleName ? `${ruleName} - 已添加账号列表` : "已添加账号列表"; + const getStatusColor = (status?: string) => { + switch (status) { + case "normal": + return "#52c41a"; + case "limited": + return "#faad14"; + case "blocked": + return "#ff4d4f"; + default: + return "#d9d9d9"; + } + }; + + const getStatusText = (status?: string) => { + switch (status) { + case "normal": + return "正常"; + case "limited": + return "受限"; + case "blocked": + return "封禁"; + default: + return "未知"; + } + }; + + return ( + +
+ {/* 头部 */} +
+

{title}

+
+ + {/* 账号列表 */} +
+ {loading ? ( +
+ +
+ 正在加载账号列表... +
+
+ ) : accounts.length > 0 ? ( + accounts.map((account, index) => ( +
+
+ +
+
+
+ {account.userinfo.nickname || + account.userinfo.alias || + `账号${account.id}`} +
+
+ {account.userinfo.wechatId || "未绑定微信号"} +
+
+
+ + + {getStatusText(account.status)} + +
+
+ )) + ) : ( +
+
暂无账号数据
+
+ )} +
+ + {/* 底部统计 */} +
+
+ 共 {accounts.length} 个账号 +
+
+
+
+ ); +}; + +export default AccountListModal; diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx new file mode 100644 index 00000000..a084dc3c --- /dev/null +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/PoolListModal.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useState } from "react"; +import { Popup, SpinLoading } from "antd-mobile"; +import { Button, message } from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import style from "./Popups.module.scss"; +import { getFriendRequestTaskStats } from "../api"; +import LineChart2 from "@/components/LineChart2"; +interface StatisticsData { + totalAll: number; + totalError: number; + totalPass: number; + totalPassRate: number; + totalSuccess: number; + totalSuccessRate: number; +} + +interface PoolListModalProps { + visible: boolean; + onClose: () => void; + ruleId?: number; + ruleName?: string; +} + +const PoolListModal: React.FC = ({ + visible, + onClose, + ruleId, + ruleName, +}) => { + const [statistics, setStatistics] = useState({ + totalAll: 0, + totalError: 0, + totalPass: 0, + totalPassRate: 0, + totalSuccess: 0, + totalSuccessRate: 0, + }); + + const [xData, setXData] = useState([]); + const [yData, setYData] = useState([]); + const [loading, setLoading] = useState(false); + + // 当弹窗打开且有ruleId时,获取数据 + useEffect(() => { + if (visible && ruleId) { + setLoading(true); + getFriendRequestTaskStats(ruleId.toString()) + .then(res => { + console.log(res); + setXData(res.dateArray); + setYData([ + res.allNumArray, + res.errorNumArray, + res.passNumArray, + res.passRateArray, + res.successNumArray, + res.successRateArray, + ]); + setStatistics(res.totalStats); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + } + }, [visible, ruleId]); + + const title = ruleName ? `${ruleName} - 累计统计数据` : "累计统计数据"; + return ( + +
+ {/* 头部 */} +
+

{title}

+
+ + {/* 统计数据表格 */} +
+ {loading ? ( +
+ +
+ 正在加载统计数据... +
+
+ ) : ( +
+
+
总计
+
+ {statistics.totalAll} +
+
+
+
扫码
+
+ {statistics.totalError} +
+
+
+
成功
+
+ {statistics.totalSuccess} +
+
+
+
失败
+
+ {statistics.totalError} +
+
+
+
通过
+
+ {statistics.totalPass} +
+
+
+
成功率
+
+ {statistics.totalSuccessRate}% +
+
+
+
通过率
+
+ {statistics.totalPassRate}% +
+
+
+ )} +
+ + {/* 趋势图占位 */} +
+
趋势图
+
+ +
+
+
+
+ ); +}; + +export default PoolListModal; diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss new file mode 100644 index 00000000..cbfce7c1 --- /dev/null +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/components/Popups.module.scss @@ -0,0 +1,744 @@ +.listToolbar { + display: flex; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f0f0f0; + background: #fff; + font-size: 16px; + color: #222; +} + +.ruleList { + padding: 0 16px; +} + +.ruleCard { + background: #fff; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + margin-bottom: 20px; + padding: 16px; + border: 1px solid #ececec; + transition: + box-shadow 0.2s, + border-color 0.2s; + position: relative; +} +.ruleCard:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + border-color: #b3e5fc; +} + +.ruleHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} +.ruleName { + font-size: 17px; + font-weight: 600; + color: #222; +} + +.ruleStatus { + display: flex; + align-items: center; + gap: 8px; +} + +.ruleSwitch { + margin-left: 4px; +} + +.ruleMenu { + margin-left: 8px; + cursor: pointer; + color: #888; + font-size: 18px; +} + +.ruleMeta { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + font-size: 15px; + color: #444; + font-weight: 500; +} +.ruleMetaItem { + flex: 1; + text-align: center; + transition: background-color 0.2s ease; +} +.ruleMetaItem:not(:last-child) { + border-right: 1px solid #f0f0f0; +} +.ruleMetaItem:hover { + background-color: #f8f9fa; + border-radius: 6px; +} + +.ruleDivider { + border-top: 1px solid #f0f0f0; + margin: 12px 0 10px 0; +} + +.ruleStats { + display: flex; + justify-content: space-between; + font-size: 16px; + color: #222; + font-weight: 600; + margin-bottom: 8px; +} +.ruleStatsItem { + flex: 1; + text-align: center; +} +.ruleStatsItem:not(:last-child) { + border-right: 1px solid #f0f0f0; +} + +.ruleFooter { + display: flex; + justify-content: space-between; + font-size: 12px; + color: #888; + margin-top: 6px; + align-items: center; +} + +.ruleFooterIcon { + margin-right: 4px; + vertical-align: middle; + font-size: 15px; + position: relative; + top: -2px; +} + +.empty { + text-align: center; + color: #bbb; + padding: 40px 0; +} + +.pagination { + display: flex; + justify-content: center; + padding: 16px 0; + background: #fff; +} + +// 账号列表弹窗样式 +.accountModal { + height: 100%; + display: flex; + flex-direction: column; +} + +.accountModalHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid #f0f0f0; + background: #fff; +} + +.accountModalTitle { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #222; +} + +.accountModalClose { + border: none; + background: none; + color: #888; + font-size: 16px; +} + +.accountList { + flex: 1; + overflow-y: auto; + padding: 0 20px; +} + +.accountItem { + display: flex; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f5f5f5; +} + +.accountItem:last-child { + border-bottom: none; +} + +.accountAvatar { + margin-right: 12px; + flex-shrink: 0; +} + +.accountInfo { + flex: 1; + min-width: 0; +} + +.accountName { + font-size: 16px; + font-weight: 500; + color: #222; + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.accountWechatId { + font-size: 14px; + color: #888; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.accountStatus { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} + +.statusDot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.statusText { + font-size: 13px; + color: #666; +} + +.accountEmpty { + display: flex; + align-items: center; + justify-content: center; + height: 200px; + color: #888; +} + +.accountEmptyText { + font-size: 16px; +} + +.accountLoading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + gap: 16px; + padding: 20px; +} + +.accountLoadingText { + font-size: 15px; + color: #666; + font-weight: 500; +} + +.accountModalFooter { + padding: 16px 20px; + border-top: 1px solid #f0f0f0; + background: #fff; +} + +.accountStats { + text-align: center; + font-size: 14px; + color: #666; +} + +// 设备列表弹窗样式 +.deviceModal { + height: 100%; + display: flex; + flex-direction: column; +} + +.deviceModalHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid #f0f0f0; + background: #fff; +} + +.deviceModalTitle { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #222; +} + +.deviceModalClose { + border: none; + background: none; + color: #888; + font-size: 16px; +} + +.deviceList { + flex: 1; + overflow-y: auto; + padding: 0 20px; +} + +.deviceItem { + background: #fff; + border-radius: 12px; + padding: 12px; + margin-bottom: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + border: 1px solid #ececec; + transition: all 0.2s ease; +} + +.deviceItem:hover { + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); +} + +.deviceHeaderRow { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.deviceImeiText { + font-size: 13px; + color: #888; + font-weight: 500; +} + +.deviceMainContent { + display: flex; + align-items: center; +} + +.deviceAvatar { + width: 48px; + height: 48px; + border-radius: 12px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + flex-shrink: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.deviceAvatar img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 12px; +} + +.deviceAvatarText { + color: #fff; + font-size: 18px; + font-weight: 600; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); +} + +.deviceInfo { + flex: 1; + min-width: 0; +} + +.deviceInfoHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 6px; +} + +.deviceName { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #222; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + margin-right: 8px; +} + +.deviceStatusBadge { + padding: 2px 8px; + border-radius: 10px; + font-size: 12px; + font-weight: 500; + flex-shrink: 0; +} + +.deviceStatusOnline { + background: rgba(82, 196, 26, 0.1); + color: #52c41a; +} + +.deviceStatusOffline { + background: rgba(255, 77, 79, 0.1); + color: #ff4d4f; +} + +.deviceInfoList { + display: flex; + flex-direction: column; + gap: 4px; +} + +.deviceInfoItem { + display: flex; + align-items: center; + font-size: 13px; +} + +.deviceInfoLabel { + color: #888; + margin-right: 6px; + min-width: 50px; +} + +.deviceInfoValue { + color: #444; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.deviceFriendCount { + color: #1890ff; + font-weight: 500; +} + +.deviceEmpty { + display: flex; + align-items: center; + justify-content: center; + height: 200px; + color: #888; +} + +.deviceEmptyText { + font-size: 16px; +} + +.deviceLoading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + gap: 16px; + padding: 20px; +} + +.deviceLoadingText { + font-size: 15px; + color: #666; + font-weight: 500; +} + +.deviceModalFooter { + padding: 16px 20px; + border-top: 1px solid #f0f0f0; + background: #fff; +} + +.deviceStats { + text-align: center; + font-size: 14px; + color: #666; +} + +// 流量池列表弹窗样式 +.poolModal { + height: 100%; + display: flex; + flex-direction: column; +} + +.poolModalHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid #f0f0f0; + background: #fff; +} + +.poolModalTitle { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #222; +} + +.poolModalClose { + border: none; + background: none; + color: #888; + font-size: 16px; +} + +.poolList { + flex: 1; + overflow-y: auto; + padding: 0 20px; +} + +.poolItem { + background: #fff; + border-radius: 12px; + padding: 12px; + margin-bottom: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + border: 1px solid #ececec; + transition: all 0.2s ease; +} + +.poolItem:hover { + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); +} + +.poolMainContent { + display: flex; + align-items: flex-start; +} + +.poolIcon { + width: 48px; + height: 48px; + border-radius: 12px; + background: linear-gradient(135deg, #1890ff 0%, #722ed1 100%); + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + flex-shrink: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.poolIconText { + color: #fff; + font-size: 18px; + font-weight: 600; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); +} + +.poolInfo { + flex: 1; + min-width: 0; +} + +.poolInfoHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 6px; +} + +.poolName { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #222; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + margin-right: 8px; +} + +.poolUserCount { + padding: 2px 8px; + border-radius: 10px; + font-size: 12px; + font-weight: 500; + background: rgba(24, 144, 255, 0.1); + color: #1890ff; + flex-shrink: 0; +} + +.poolInfoList { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 8px; +} + +.poolInfoItem { + display: flex; + align-items: center; + font-size: 13px; +} + +.poolInfoLabel { + color: #888; + margin-right: 6px; + min-width: 60px; +} + +.poolInfoValue { + color: #444; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.poolTags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.poolTag { + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + background: rgba(0, 0, 0, 0.05); + color: #666; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.poolLoading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + gap: 16px; + padding: 20px; +} + +.poolLoadingText { + font-size: 15px; + color: #666; + font-weight: 500; +} + +.poolEmpty { + display: flex; + align-items: center; + justify-content: center; + height: 200px; + color: #888; +} + +.poolEmptyText { + font-size: 16px; +} + +.poolModalFooter { + padding: 16px 20px; + border-top: 1px solid #f0f0f0; + background: #fff; +} + +.poolStats { + text-align: center; + font-size: 14px; + color: #666; +} + +// 统计数据弹窗样式 +.statisticsContent { + flex: 1; + overflow-y: auto; + padding: 0 20px; +} + +.statisticsLoading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + gap: 16px; + padding: 20px; +} + +.statisticsLoadingText { + font-size: 15px; + color: #666; + font-weight: 500; +} + +.statisticsTable { + padding: 16px 0; +} + +.statisticsRow { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0; + border-bottom: 1px solid #f5f5f5; +} + +.statisticsRow:last-child { + border-bottom: none; +} + +.statisticsLabel { + font-size: 15px; + color: #666; + font-weight: 500; +} + +.statisticsValue { + font-size: 16px; + color: #222; + font-weight: 600; +} + +.trendChart { + padding: 20px; + border-top: 1px solid #f0f0f0; + background: #fff; +} + +.chartTitle { + font-size: 16px; + font-weight: 600; + color: #222; + margin-bottom: 16px; +} + +.chartPlaceholder { + height: 200px; + background: #f8f9fa; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + border: 2px dashed #d9d9d9; +} + +.chartNote { + font-size: 14px; + color: #888; + text-align: center; +} diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/data.ts b/Cunkebao/src/pages/mobile/scenarios/plan/list/data.ts index 9c1397f8..77e1df19 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/list/data.ts +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/data.ts @@ -20,6 +20,7 @@ export interface Task { acquiredCount?: number; addedCount?: number; passRate?: number; + passCount?: number; } export interface ApiSettings { diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/index.module.scss b/Cunkebao/src/pages/mobile/scenarios/plan/list/index.module.scss index 0b574ee6..d5fc2004 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/list/index.module.scss +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/index.module.scss @@ -139,6 +139,25 @@ padding: 12px; text-align: center; border: 1px solid #e9ecef; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + + &:hover { + background: #e6f7ff; + border-color: #91d5ff; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15); + } + + &:active { + transform: translateY(0); + box-shadow: 0 1px 4px rgba(24, 144, 255, 0.1); + } + + &:hover::after { + opacity: 1; + } } .stat-label { diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx index 9981d48e..5e3573c1 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx @@ -35,6 +35,10 @@ import style from "./index.module.scss"; import { Task, ApiSettings, PlanDetail } from "./data"; import PlanApi from "./planApi"; import { buildApiUrl } from "@/utils/apiUrl"; +import DeviceListModal from "./components/DeviceListModal"; +import AccountListModal from "./components/AccountListModal"; +import OreadyAdd from "./components/OreadyAdd"; +import PoolListModal from "./components/PoolListModal"; const ScenarioList: React.FC = () => { const { scenarioId, scenarioName } = useParams<{ @@ -58,6 +62,19 @@ const ScenarioList: React.FC = () => { const [currentTaskId, setCurrentTaskId] = useState(""); const [showActionMenu, setShowActionMenu] = useState(null); + // 设备列表弹窗状态 + const [showDeviceList, setShowDeviceList] = useState(false); + const [currentTask, setCurrentTask] = useState(null); + + // 账号列表弹窗状态 + const [showAccountList, setShowAccountList] = useState(false); + + // 已添加弹窗状态 + const [showOreadyAdd, setShowOreadyAdd] = useState(false); + + // 通过率弹窗状态 + const [showPoolList, setShowPoolList] = useState(false); + // 分页相关状态 const [currentPage, setCurrentPage] = useState(1); const [hasMore, setHasMore] = useState(true); @@ -233,19 +250,28 @@ const ScenarioList: React.FC = () => { } }; - // 卡片点击处理 - 执行二维码动作 - const handleCardClick = (taskId: string, event: React.MouseEvent) => { - // 检查点击是否在更多按钮区域内 - const target = event.target as HTMLElement; - const moreButton = target.closest(`.${style["more-btn"]}`); + // 处理设备列表弹窗 + const handleShowDeviceList = (task: Task) => { + setCurrentTask(task); + setShowDeviceList(true); + }; - // 如果点击的是更多按钮或其子元素,不执行卡片点击动作 - if (moreButton) { - return; - } + // 处理账号列表弹窗 + const handleShowAccountList = (task: Task) => { + setCurrentTask(task); + setShowAccountList(true); + }; - // 执行二维码动作 - handleShowQrCode(taskId); + // 处理已添加弹窗 + const handleShowOreadyAdd = (task: Task) => { + setCurrentTask(task); + setShowOreadyAdd(true); + }; + + // 处理通过率弹窗 + const handleShowPoolList = (task: Task) => { + setCurrentTask(task); + setShowPoolList(true); }; const getStatusColor = (status: number) => { @@ -433,25 +459,49 @@ const ScenarioList: React.FC = () => { {/* 统计数据网格 */}
-
+
{ + e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 + handleShowDeviceList(task); + }} + >
设备数
{deviceCount(task)}
-
+
{ + e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 + handleShowAccountList(task); + }} + >
已获客
{task?.acquiredCount || 0}
-
+
{ + e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 + handleShowOreadyAdd(task); + }} + >
已添加
- {task.addedCount || 0} + {task.passCount || 0}
-
+
{ + e.stopPropagation(); // 阻止事件冒泡,避免触发卡片点击 + handleShowPoolList(task); + }} + >
通过率
{task.passRate}% @@ -581,6 +631,38 @@ const ScenarioList: React.FC = () => {
+ + {/* 设备列表弹窗 */} + setShowDeviceList(false)} + ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} + ruleName={currentTask?.name} + /> + + {/* 账号列表弹窗 */} + setShowAccountList(false)} + ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} + ruleName={currentTask?.name} + /> + + {/* 已添加弹窗 */} + setShowOreadyAdd(false)} + ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} + ruleName={currentTask?.name} + /> + + {/* 通过率弹窗 */} + setShowPoolList(false)} + ruleId={currentTask?.id ? parseInt(currentTask.id) : undefined} + ruleName={currentTask?.name} + />
); diff --git a/Cunkebao/src/utils/chartColors.ts b/Cunkebao/src/utils/chartColors.ts new file mode 100644 index 00000000..6290fab9 --- /dev/null +++ b/Cunkebao/src/utils/chartColors.ts @@ -0,0 +1,67 @@ +// 预定义的颜色数组,确保颜色不重复且美观 +const CHART_COLORS = [ + "#1677ff", // 蓝色 + "#52c41a", // 绿色 + "#fa8c16", // 橙色 + "#eb2f96", // 粉色 + "#722ed1", // 紫色 + "#13c2c2", // 青色 + "#fa541c", // 红色 + "#2f54eb", // 深蓝色 + "#faad14", // 黄色 + "#a0d911", // 青绿色 + "#f5222d", // 红色 + "#1890ff", // 天蓝色 + "#52c41a", // 绿色 + "#fa8c16", // 橙色 + "#eb2f96", // 粉色 +]; + +/** + * 获取图表颜色 + * @param index 颜色索引 + * @returns 颜色值 + */ +export const getChartColor = (index: number): string => { + return CHART_COLORS[index % CHART_COLORS.length]; +}; + +/** + * 获取多个图表颜色 + * @param count 需要的颜色数量 + * @returns 颜色数组 + */ +export const getChartColors = (count: number): string[] => { + return Array.from({ length: count }, (_, index) => getChartColor(index)); +}; + +/** + * 获取随机图表颜色 + * @returns 随机颜色值 + */ +export const getRandomChartColor = (): string => { + const randomIndex = Math.floor(Math.random() * CHART_COLORS.length); + return CHART_COLORS[randomIndex]; +}; + +/** + * 获取渐变色数组 + * @param baseColor 基础颜色 + * @param count 渐变数量 + * @returns 渐变色数组 + */ +export const getGradientColors = ( + baseColor: string, + count: number, +): string[] => { + // 这里可以实现颜色渐变逻辑 + // 暂时返回相同颜色的数组 + return Array.from({ length: count }, () => baseColor); +}; + +export default { + getChartColor, + getChartColors, + getRandomChartColor, + getGradientColors, +};