From 7be8ff2e940b86fd09a6db624d95b861e7187bf4 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: Thu, 14 Aug 2025 16:53:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=AA=E9=99=A4=E5=B8=B3=E8=99=9F=E5=92=8C?= =?UTF-8?q?=E8=A8=AD=E5=82=99=E5=88=97=E8=A1=A8=E5=BD=88=E7=AA=97=E7=B5=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=B5=81=E9=87=8F=E6=B1=A0?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=BD=88=E7=AA=97=E7=B5=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E4=B8=A6=E6=9B=B4=E6=96=B0=E6=A8=A3=E5=BC=8F=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=96=B0=E7=9A=84=E5=BD=88=E7=AA=97=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E5=84=AA=E5=8C=96=E6=B5=81=E9=87=8F=E5=88=86=E9=85=8D?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9A=84=E9=A1=AF=E7=A4=BA=E9=82=8F=E8=BC=AF?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../traffic-distribution/list/api.ts | 5 + .../{ => components}/AccountListModal.tsx | 64 ++++- .../list/{ => components}/DeviceListModal.tsx | 52 +++- .../list/components/PoolListModal.tsx | 170 +++++++++++++ .../list/index.module.scss | 223 ++++++++++++++++++ .../traffic-distribution/list/index.tsx | 72 +++--- 6 files changed, 524 insertions(+), 62 deletions(-) rename Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/{ => components}/AccountListModal.tsx (64%) rename Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/{ => components}/DeviceListModal.tsx (75%) create mode 100644 Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/PoolListModal.tsx diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts index ceacca1c..93c5002e 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/api.ts @@ -26,3 +26,8 @@ export function toggleDistributionRuleStatus( export function deleteDistributionRule(id: number): Promise { return request("/v1/workbench/delete", { id }, "POST"); } + +// 获取流量分发规则详情 +export function fetchDistributionRuleDetail(id: number): Promise { + return request(`/v1/workbench/detail?id=${id}`, {}, "GET"); +} diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/AccountListModal.tsx b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/AccountListModal.tsx similarity index 64% rename from Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/AccountListModal.tsx rename to Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/AccountListModal.tsx index 79bc23a7..c792f143 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/AccountListModal.tsx +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/AccountListModal.tsx @@ -1,8 +1,9 @@ -import React from "react"; -import { Popup, Avatar } from "antd-mobile"; -import { Button } from "antd"; +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 "./index.module.scss"; +import style from "../index.module.scss"; +import { fetchDistributionRuleDetail } from "../api"; interface AccountItem { id: string | number; @@ -15,16 +16,44 @@ interface AccountItem { interface AccountListModalProps { visible: boolean; onClose: () => void; - accounts: AccountItem[]; - title?: string; + ruleId?: number; + ruleName?: string; } const AccountListModal: React.FC = ({ visible, onClose, - accounts, - title = "分发账号列表", + ruleId, + ruleName, }) => { + const [accounts, setAccounts] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取账号数据 + const fetchAccounts = async () => { + if (!ruleId) return; + + setLoading(true); + try { + const detailRes = await fetchDistributionRuleDetail(ruleId); + const accountData = detailRes?.config?.accountGroupsOptions || []; + 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": @@ -76,13 +105,24 @@ const AccountListModal: React.FC = ({ {/* 账号列表 */}
- {accounts.length > 0 ? ( + {loading ? ( +
+ +
+ 正在加载账号列表... +
+
+ ) : accounts.length > 0 ? ( accounts.map((account, index) => (
- - {(account.nickname || account.wechatId || "账号")[0]} - +
diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/DeviceListModal.tsx b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/DeviceListModal.tsx similarity index 75% rename from Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/DeviceListModal.tsx rename to Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/DeviceListModal.tsx index ea26f58e..cd75f942 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/DeviceListModal.tsx +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/DeviceListModal.tsx @@ -1,8 +1,9 @@ -import React from "react"; -import { Popup, Avatar } from "antd-mobile"; -import { Button } from "antd"; +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 "./index.module.scss"; +import style from "../index.module.scss"; +import { fetchDistributionRuleDetail } from "../api"; interface DeviceItem { id: string | number; @@ -17,16 +18,44 @@ interface DeviceItem { interface DeviceListModalProps { visible: boolean; onClose: () => void; - devices: DeviceItem[]; - title?: string; + ruleId?: number; + ruleName?: string; } const DeviceListModal: React.FC = ({ visible, onClose, - devices, - title = "分发设备列表", + ruleId, + ruleName, }) => { + const [devices, setDevices] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取设备数据 + const fetchDevices = async () => { + if (!ruleId) return; + + setLoading(true); + try { + const detailRes = await fetchDistributionRuleDetail(ruleId); + const deviceData = detailRes?.config?.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"; }; @@ -60,7 +89,12 @@ const DeviceListModal: React.FC = ({ {/* 设备列表 */}
- {devices.length > 0 ? ( + {loading ? ( +
+ +
正在加载设备列表...
+
+ ) : devices.length > 0 ? ( devices.map((device, index) => (
{/* 顶部行:IMEI */} diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/PoolListModal.tsx b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/PoolListModal.tsx new file mode 100644 index 00000000..d411bac8 --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/components/PoolListModal.tsx @@ -0,0 +1,170 @@ +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 "../index.module.scss"; +import { fetchDistributionRuleDetail } from "../api"; + +interface PoolItem { + id: string | number; + name?: string; + description?: string; + userCount?: number; + tags?: string[]; + createdAt?: string; + deviceIds?: string[]; +} + +interface PoolListModalProps { + visible: boolean; + onClose: () => void; + ruleId?: number; + ruleName?: string; +} + +const PoolListModal: React.FC = ({ + visible, + onClose, + ruleId, + ruleName, +}) => { + const [pools, setPools] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取流量池数据 + const fetchPools = async () => { + if (!ruleId) return; + + setLoading(true); + try { + const detailRes = await fetchDistributionRuleDetail(ruleId); + const poolData = detailRes?.config?.pools || []; + + const formattedPools = poolData.map((pool: any) => ({ + id: pool.id || pool.poolId, + name: pool.name || pool.poolName || `流量池${pool.id}`, + description: pool.description || pool.desc || "", + userCount: pool.userCount || pool.count || 0, + tags: pool.tags || [], + createdAt: pool.createdAt || pool.createTime || "", + deviceIds: pool.deviceIds || [], + })); + + setPools(formattedPools); + } catch (error) { + console.error("获取流量池详情失败:", error); + message.error("获取流量池详情失败"); + } finally { + setLoading(false); + } + }; + + // 当弹窗打开且有ruleId时,获取数据 + useEffect(() => { + if (visible && ruleId) { + fetchPools(); + } + }, [visible, ruleId]); + + const title = ruleName ? `${ruleName} - 流量池列表` : "流量池列表"; + return ( + +
+ {/* 头部 */} +
+

{title}

+
+ + {/* 流量池列表 */} +
+ {loading ? ( +
+ +
正在加载流量池列表...
+
+ ) : pools.length > 0 ? ( + pools.map((pool, index) => ( +
+ {/* 流量池信息 */} +
+ {/* 图标 */} +
+ + {(pool.name || "池")[0]} + +
+ + {/* 流量池信息 */} +
+
+

+ {pool.name || `流量池${pool.id}`} +

+ + {pool.userCount || 0} 人 + +
+ +
+
+ 描述: + + {pool.description || "暂无描述"} + +
+
+ 创建时间: + + {pool.createdAt || "-"} + +
+
+ + {/* 标签 */} + {pool.tags && pool.tags.length > 0 && ( +
+ {pool.tags.map((tag, tagIndex) => ( + + {tag} + + ))} +
+ )} +
+
+
+ )) + ) : ( +
+
暂无流量池数据
+
+ )} +
+ + {/* 底部统计 */} +
+
+ 共 {pools.length} 个流量池 +
+
+
+
+ ); +}; + +export default PoolListModal; diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss index f30dfa28..9f5adc14 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.module.scss @@ -235,6 +235,22 @@ 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; @@ -428,6 +444,22 @@ 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; @@ -439,3 +471,194 @@ 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; +} diff --git a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.tsx b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.tsx index 0b571ecf..c636c8fc 100644 --- a/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.tsx +++ b/Cunkebao/src/pages/mobile/workspace/traffic-distribution/list/index.tsx @@ -32,8 +32,9 @@ import { } from "@ant-design/icons"; import style from "./index.module.scss"; import { useNavigate } from "react-router-dom"; -import AccountListModal from "./AccountListModal"; -import DeviceListModal from "./DeviceListModal"; +import AccountListModal from "./components/AccountListModal"; +import DeviceListModal from "./components/DeviceListModal"; +import PoolListModal from "./components/PoolListModal"; const PAGE_SIZE = 10; @@ -53,14 +54,11 @@ const TrafficDistributionList: React.FC = () => { const [searchQuery, setSearchQuery] = useState(""); // 优化:用menuLoadingId标记当前操作的item const [menuLoadingId, setMenuLoadingId] = useState(null); - // 账号列表弹窗 + // 弹窗控制 const [accountModalVisible, setAccountModalVisible] = useState(false); - const [currentAccounts, setCurrentAccounts] = useState([]); - const [currentRuleName, setCurrentRuleName] = useState(""); - // 设备列表弹窗 const [deviceModalVisible, setDeviceModalVisible] = useState(false); - const [currentDevices, setCurrentDevices] = useState([]); - const [currentDeviceRuleName, setCurrentDeviceRuleName] = useState(""); + const [poolModalVisible, setPoolModalVisible] = useState(false); + const [currentRule, setCurrentRule] = useState(null); const navigate = useNavigate(); useEffect(() => { @@ -140,42 +138,22 @@ const TrafficDistributionList: React.FC = () => { // 显示账号列表弹窗 const showAccountList = (item: DistributionRule) => { - // 这里需要根据实际的账号数据结构来转换 - // 假设 item.config.account 是账号ID数组,需要转换为账号对象数组 - const accounts = (item.config?.account || []).map( - (accountId: string | number) => ({ - id: accountId, - nickname: `账号${accountId}`, - wechatId: `wx_${accountId}`, - avatar: "", - status: "normal", - }), - ); - - setCurrentAccounts(accounts); - setCurrentRuleName(item.name); + setCurrentRule(item); setAccountModalVisible(true); }; // 显示设备列表弹窗 const showDeviceList = (item: DistributionRule) => { - // 这里需要根据实际的设备数据结构来转换 - // 假设 item.config.devices 是设备ID数组,需要转换为设备对象数组 - const devices = (item.config?.devices || []).map((deviceId: string) => ({ - id: deviceId, - memo: `设备${deviceId}`, - imei: `IMEI${deviceId}`, - wechatId: `wx_${deviceId}`, - status: "online" as const, - avatar: "", - totalFriend: Math.floor(Math.random() * 1000) + 100, - })); - - setCurrentDevices(devices); - setCurrentDeviceRuleName(item.name); + setCurrentRule(item); setDeviceModalVisible(true); }; + // 显示流量池列表弹窗 + const showPoolList = (item: DistributionRule) => { + setCurrentRule(item); + setPoolModalVisible(true); + }; + const renderCard = (item: DistributionRule) => { const menu = ( handleMenuClick(key, item)}> @@ -281,7 +259,11 @@ const TrafficDistributionList: React.FC = () => {
分发设备
-
+
showPoolList(item)} + >
{item.config?.pools?.length || 0}
@@ -386,16 +368,24 @@ const TrafficDistributionList: React.FC = () => { setAccountModalVisible(false)} - accounts={currentAccounts} - title={`${currentRuleName} - 分发账号列表`} + ruleId={currentRule?.id} + ruleName={currentRule?.name} /> {/* 设备列表弹窗 */} setDeviceModalVisible(false)} - devices={currentDevices} - title={`${currentDeviceRuleName} - 分发设备列表`} + ruleId={currentRule?.id} + ruleName={currentRule?.name} + /> + + {/* 流量池列表弹窗 */} + setPoolModalVisible(false)} + ruleId={currentRule?.id} + ruleName={currentRule?.name} /> );