刪除帳號和設備列表彈窗組件,新增流量池列表彈窗組件,並更新樣式以支持新的彈窗功能,優化流量分配列表的顯示邏輯。
This commit is contained in:
@@ -26,3 +26,8 @@ export function toggleDistributionRuleStatus(
|
|||||||
export function deleteDistributionRule(id: number): Promise<any> {
|
export function deleteDistributionRule(id: number): Promise<any> {
|
||||||
return request("/v1/workbench/delete", { id }, "POST");
|
return request("/v1/workbench/delete", { id }, "POST");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取流量分发规则详情
|
||||||
|
export function fetchDistributionRuleDetail(id: number): Promise<any> {
|
||||||
|
return request(`/v1/workbench/detail?id=${id}`, {}, "GET");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Popup, Avatar } from "antd-mobile";
|
import { Popup, Avatar, SpinLoading } from "antd-mobile";
|
||||||
import { Button } from "antd";
|
import { Button, message } from "antd";
|
||||||
import { CloseOutlined } from "@ant-design/icons";
|
import { CloseOutlined } from "@ant-design/icons";
|
||||||
import style from "./index.module.scss";
|
import style from "../index.module.scss";
|
||||||
|
import { fetchDistributionRuleDetail } from "../api";
|
||||||
|
|
||||||
interface AccountItem {
|
interface AccountItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
@@ -15,16 +16,44 @@ interface AccountItem {
|
|||||||
interface AccountListModalProps {
|
interface AccountListModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
accounts: AccountItem[];
|
ruleId?: number;
|
||||||
title?: string;
|
ruleName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountListModal: React.FC<AccountListModalProps> = ({
|
const AccountListModal: React.FC<AccountListModalProps> = ({
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
accounts,
|
ruleId,
|
||||||
title = "分发账号列表",
|
ruleName,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [accounts, setAccounts] = useState<AccountItem[]>([]);
|
||||||
|
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) => {
|
const getStatusColor = (status?: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "normal":
|
case "normal":
|
||||||
@@ -76,13 +105,24 @@ const AccountListModal: React.FC<AccountListModalProps> = ({
|
|||||||
|
|
||||||
{/* 账号列表 */}
|
{/* 账号列表 */}
|
||||||
<div className={style.accountList}>
|
<div className={style.accountList}>
|
||||||
{accounts.length > 0 ? (
|
{loading ? (
|
||||||
|
<div className={style.accountLoading}>
|
||||||
|
<SpinLoading color="primary" />
|
||||||
|
<div className={style.accountLoadingText}>
|
||||||
|
正在加载账号列表...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : accounts.length > 0 ? (
|
||||||
accounts.map((account, index) => (
|
accounts.map((account, index) => (
|
||||||
<div key={account.id || index} className={style.accountItem}>
|
<div key={account.id || index} className={style.accountItem}>
|
||||||
<div className={style.accountAvatar}>
|
<div className={style.accountAvatar}>
|
||||||
<Avatar src={account.avatar} style={{ "--size": "48px" }}>
|
<Avatar
|
||||||
{(account.nickname || account.wechatId || "账号")[0]}
|
src={account.avatar}
|
||||||
</Avatar>
|
style={{ "--size": "48px" }}
|
||||||
|
fallback={
|
||||||
|
(account.nickname || account.wechatId || "账号")[0]
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.accountInfo}>
|
<div className={style.accountInfo}>
|
||||||
<div className={style.accountName}>
|
<div className={style.accountName}>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Popup, Avatar } from "antd-mobile";
|
import { Popup, Avatar, SpinLoading } from "antd-mobile";
|
||||||
import { Button } from "antd";
|
import { Button, message } from "antd";
|
||||||
import { CloseOutlined } from "@ant-design/icons";
|
import { CloseOutlined } from "@ant-design/icons";
|
||||||
import style from "./index.module.scss";
|
import style from "../index.module.scss";
|
||||||
|
import { fetchDistributionRuleDetail } from "../api";
|
||||||
|
|
||||||
interface DeviceItem {
|
interface DeviceItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
@@ -17,16 +18,44 @@ interface DeviceItem {
|
|||||||
interface DeviceListModalProps {
|
interface DeviceListModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
devices: DeviceItem[];
|
ruleId?: number;
|
||||||
title?: string;
|
ruleName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeviceListModal: React.FC<DeviceListModalProps> = ({
|
const DeviceListModal: React.FC<DeviceListModalProps> = ({
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
devices,
|
ruleId,
|
||||||
title = "分发设备列表",
|
ruleName,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [devices, setDevices] = useState<DeviceItem[]>([]);
|
||||||
|
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) => {
|
const getStatusColor = (status?: string) => {
|
||||||
return status === "online" ? "#52c41a" : "#ff4d4f";
|
return status === "online" ? "#52c41a" : "#ff4d4f";
|
||||||
};
|
};
|
||||||
@@ -60,7 +89,12 @@ const DeviceListModal: React.FC<DeviceListModalProps> = ({
|
|||||||
|
|
||||||
{/* 设备列表 */}
|
{/* 设备列表 */}
|
||||||
<div className={style.deviceList}>
|
<div className={style.deviceList}>
|
||||||
{devices.length > 0 ? (
|
{loading ? (
|
||||||
|
<div className={style.deviceLoading}>
|
||||||
|
<SpinLoading color="primary" />
|
||||||
|
<div className={style.deviceLoadingText}>正在加载设备列表...</div>
|
||||||
|
</div>
|
||||||
|
) : devices.length > 0 ? (
|
||||||
devices.map((device, index) => (
|
devices.map((device, index) => (
|
||||||
<div key={device.id || index} className={style.deviceItem}>
|
<div key={device.id || index} className={style.deviceItem}>
|
||||||
{/* 顶部行:IMEI */}
|
{/* 顶部行:IMEI */}
|
||||||
@@ -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<PoolListModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
ruleId,
|
||||||
|
ruleName,
|
||||||
|
}) => {
|
||||||
|
const [pools, setPools] = useState<PoolItem[]>([]);
|
||||||
|
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 (
|
||||||
|
<Popup
|
||||||
|
visible={visible}
|
||||||
|
onMaskClick={onClose}
|
||||||
|
position="bottom"
|
||||||
|
bodyStyle={{
|
||||||
|
height: "70vh",
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={style.poolModal}>
|
||||||
|
{/* 头部 */}
|
||||||
|
<div className={style.poolModalHeader}>
|
||||||
|
<h3 className={style.poolModalTitle}>{title}</h3>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
onClick={onClose}
|
||||||
|
className={style.poolModalClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 流量池列表 */}
|
||||||
|
<div className={style.poolList}>
|
||||||
|
{loading ? (
|
||||||
|
<div className={style.poolLoading}>
|
||||||
|
<SpinLoading color="primary" />
|
||||||
|
<div className={style.poolLoadingText}>正在加载流量池列表...</div>
|
||||||
|
</div>
|
||||||
|
) : pools.length > 0 ? (
|
||||||
|
pools.map((pool, index) => (
|
||||||
|
<div key={pool.id || index} className={style.poolItem}>
|
||||||
|
{/* 流量池信息 */}
|
||||||
|
<div className={style.poolMainContent}>
|
||||||
|
{/* 图标 */}
|
||||||
|
<div className={style.poolIcon}>
|
||||||
|
<span className={style.poolIconText}>
|
||||||
|
{(pool.name || "池")[0]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 流量池信息 */}
|
||||||
|
<div className={style.poolInfo}>
|
||||||
|
<div className={style.poolInfoHeader}>
|
||||||
|
<h3 className={style.poolName}>
|
||||||
|
{pool.name || `流量池${pool.id}`}
|
||||||
|
</h3>
|
||||||
|
<span className={style.poolUserCount}>
|
||||||
|
{pool.userCount || 0} 人
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style.poolInfoList}>
|
||||||
|
<div className={style.poolInfoItem}>
|
||||||
|
<span className={style.poolInfoLabel}>描述:</span>
|
||||||
|
<span className={style.poolInfoValue}>
|
||||||
|
{pool.description || "暂无描述"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={style.poolInfoItem}>
|
||||||
|
<span className={style.poolInfoLabel}>创建时间:</span>
|
||||||
|
<span className={style.poolInfoValue}>
|
||||||
|
{pool.createdAt || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 标签 */}
|
||||||
|
{pool.tags && pool.tags.length > 0 && (
|
||||||
|
<div className={style.poolTags}>
|
||||||
|
{pool.tags.map((tag, tagIndex) => (
|
||||||
|
<span key={tagIndex} className={style.poolTag}>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className={style.poolEmpty}>
|
||||||
|
<div className={style.poolEmptyText}>暂无流量池数据</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部统计 */}
|
||||||
|
<div className={style.poolModalFooter}>
|
||||||
|
<div className={style.poolStats}>
|
||||||
|
<span>共 {pools.length} 个流量池</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PoolListModal;
|
||||||
@@ -235,6 +235,22 @@
|
|||||||
font-size: 16px;
|
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 {
|
.accountModalFooter {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
@@ -428,6 +444,22 @@
|
|||||||
font-size: 16px;
|
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 {
|
.deviceModalFooter {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
@@ -439,3 +471,194 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ import {
|
|||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import AccountListModal from "./AccountListModal";
|
import AccountListModal from "./components/AccountListModal";
|
||||||
import DeviceListModal from "./DeviceListModal";
|
import DeviceListModal from "./components/DeviceListModal";
|
||||||
|
import PoolListModal from "./components/PoolListModal";
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
@@ -53,14 +54,11 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
// 优化:用menuLoadingId标记当前操作的item
|
// 优化:用menuLoadingId标记当前操作的item
|
||||||
const [menuLoadingId, setMenuLoadingId] = useState<number | null>(null);
|
const [menuLoadingId, setMenuLoadingId] = useState<number | null>(null);
|
||||||
// 账号列表弹窗
|
// 弹窗控制
|
||||||
const [accountModalVisible, setAccountModalVisible] = useState(false);
|
const [accountModalVisible, setAccountModalVisible] = useState(false);
|
||||||
const [currentAccounts, setCurrentAccounts] = useState<any[]>([]);
|
|
||||||
const [currentRuleName, setCurrentRuleName] = useState("");
|
|
||||||
// 设备列表弹窗
|
|
||||||
const [deviceModalVisible, setDeviceModalVisible] = useState(false);
|
const [deviceModalVisible, setDeviceModalVisible] = useState(false);
|
||||||
const [currentDevices, setCurrentDevices] = useState<any[]>([]);
|
const [poolModalVisible, setPoolModalVisible] = useState(false);
|
||||||
const [currentDeviceRuleName, setCurrentDeviceRuleName] = useState("");
|
const [currentRule, setCurrentRule] = useState<DistributionRule | null>(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -140,42 +138,22 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
|
|
||||||
// 显示账号列表弹窗
|
// 显示账号列表弹窗
|
||||||
const showAccountList = (item: DistributionRule) => {
|
const showAccountList = (item: DistributionRule) => {
|
||||||
// 这里需要根据实际的账号数据结构来转换
|
setCurrentRule(item);
|
||||||
// 假设 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);
|
|
||||||
setAccountModalVisible(true);
|
setAccountModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 显示设备列表弹窗
|
// 显示设备列表弹窗
|
||||||
const showDeviceList = (item: DistributionRule) => {
|
const showDeviceList = (item: DistributionRule) => {
|
||||||
// 这里需要根据实际的设备数据结构来转换
|
setCurrentRule(item);
|
||||||
// 假设 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);
|
|
||||||
setDeviceModalVisible(true);
|
setDeviceModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 显示流量池列表弹窗
|
||||||
|
const showPoolList = (item: DistributionRule) => {
|
||||||
|
setCurrentRule(item);
|
||||||
|
setPoolModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
const renderCard = (item: DistributionRule) => {
|
const renderCard = (item: DistributionRule) => {
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu onClick={({ key }) => handleMenuClick(key, item)}>
|
<Menu onClick={({ key }) => handleMenuClick(key, item)}>
|
||||||
@@ -281,7 +259,11 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: 13, color: "#888" }}>分发设备</div>
|
<div style={{ fontSize: 13, color: "#888" }}>分发设备</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.ruleMetaItem}>
|
<div
|
||||||
|
className={style.ruleMetaItem}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => showPoolList(item)}
|
||||||
|
>
|
||||||
<div style={{ fontSize: 18, fontWeight: 600 }}>
|
<div style={{ fontSize: 18, fontWeight: 600 }}>
|
||||||
{item.config?.pools?.length || 0}
|
{item.config?.pools?.length || 0}
|
||||||
</div>
|
</div>
|
||||||
@@ -386,16 +368,24 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
<AccountListModal
|
<AccountListModal
|
||||||
visible={accountModalVisible}
|
visible={accountModalVisible}
|
||||||
onClose={() => setAccountModalVisible(false)}
|
onClose={() => setAccountModalVisible(false)}
|
||||||
accounts={currentAccounts}
|
ruleId={currentRule?.id}
|
||||||
title={`${currentRuleName} - 分发账号列表`}
|
ruleName={currentRule?.name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 设备列表弹窗 */}
|
{/* 设备列表弹窗 */}
|
||||||
<DeviceListModal
|
<DeviceListModal
|
||||||
visible={deviceModalVisible}
|
visible={deviceModalVisible}
|
||||||
onClose={() => setDeviceModalVisible(false)}
|
onClose={() => setDeviceModalVisible(false)}
|
||||||
devices={currentDevices}
|
ruleId={currentRule?.id}
|
||||||
title={`${currentDeviceRuleName} - 分发设备列表`}
|
ruleName={currentRule?.name}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 流量池列表弹窗 */}
|
||||||
|
<PoolListModal
|
||||||
|
visible={poolModalVisible}
|
||||||
|
onClose={() => setPoolModalVisible(false)}
|
||||||
|
ruleId={currentRule?.id}
|
||||||
|
ruleName={currentRule?.name}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user