新增流量分發記錄功能,更新樣式以改善搜索條和分發統計彈窗的顯示效果,並在列表中整合相應的功能邏輯。
This commit is contained in:
@@ -31,3 +31,13 @@ export function deleteDistributionRule(id: number): Promise<any> {
|
|||||||
export function fetchDistributionRuleDetail(id: number): Promise<any> {
|
export function fetchDistributionRuleDetail(id: number): Promise<any> {
|
||||||
return request(`/v1/workbench/detail?id=${id}`, {}, "GET");
|
return request(`/v1/workbench/detail?id=${id}`, {}, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//流量分发记录
|
||||||
|
export function fetchTransferFriends(params: {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
keyword?: string;
|
||||||
|
workbenchId: number;
|
||||||
|
}) {
|
||||||
|
return request("/v1/workbench/transfer-friends", params, "GET");
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Popup, Avatar, SpinLoading, Input } from "antd-mobile";
|
||||||
|
import { Button, message, Pagination } from "antd";
|
||||||
|
import { CloseOutlined, SearchOutlined } from "@ant-design/icons";
|
||||||
|
import style from "../index.module.scss";
|
||||||
|
import { fetchTransferFriends } from "../api";
|
||||||
|
|
||||||
|
interface SendRecordItem {
|
||||||
|
id: string | number;
|
||||||
|
nickname?: string;
|
||||||
|
wechatId?: string;
|
||||||
|
avatar?: string;
|
||||||
|
status?: string;
|
||||||
|
isRecycle?: number;
|
||||||
|
sendTime?: string;
|
||||||
|
sendCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SendRcrodModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
ruleId?: number;
|
||||||
|
ruleName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SendRcrodModal: React.FC<SendRcrodModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
ruleId,
|
||||||
|
ruleName,
|
||||||
|
}) => {
|
||||||
|
const [sendRecords, setSendRecords] = useState<SendRecordItem[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [searchKeyword, setSearchKeyword] = useState("");
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const pageSize = 20;
|
||||||
|
|
||||||
|
// 获取分发记录数据
|
||||||
|
const fetchSendRecords = async (page = 1, keyword = "") => {
|
||||||
|
if (!ruleId || !visible) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const detailRes = await fetchTransferFriends({
|
||||||
|
workbenchId: ruleId,
|
||||||
|
page,
|
||||||
|
limit: pageSize,
|
||||||
|
keyword,
|
||||||
|
});
|
||||||
|
console.log(detailRes);
|
||||||
|
|
||||||
|
const recordData = detailRes.list || [];
|
||||||
|
setSendRecords(recordData);
|
||||||
|
setTotal(detailRes.total || 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取分发记录失败:", error);
|
||||||
|
message.error("获取分发记录失败");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当弹窗打开且有ruleId时,获取数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && ruleId) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
setSearchQuery("");
|
||||||
|
setSearchKeyword("");
|
||||||
|
fetchSendRecords(1, "");
|
||||||
|
}
|
||||||
|
}, [visible, ruleId]);
|
||||||
|
|
||||||
|
// 搜索关键词变化时触发搜索
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible || !ruleId) return;
|
||||||
|
setCurrentPage(1);
|
||||||
|
fetchSendRecords(1, searchKeyword);
|
||||||
|
}, [searchKeyword, visible, ruleId]);
|
||||||
|
|
||||||
|
// 页码变化
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible || !ruleId || currentPage === 1) return;
|
||||||
|
fetchSendRecords(currentPage, searchKeyword);
|
||||||
|
}, [currentPage, visible, ruleId]);
|
||||||
|
|
||||||
|
// 处理页码变化
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理搜索回车
|
||||||
|
const handleSearchEnter = () => {
|
||||||
|
setSearchKeyword(searchQuery);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理搜索输入
|
||||||
|
const handleSearchChange = (value: string) => {
|
||||||
|
setSearchQuery(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = ruleName ? `${ruleName} - 分发统计` : "分发统计";
|
||||||
|
const getRecycleColor = (isRecycle?: number) => {
|
||||||
|
switch (isRecycle) {
|
||||||
|
case 0:
|
||||||
|
return "#52c41a"; // 绿色 - 未回收
|
||||||
|
case 1:
|
||||||
|
return "#ff4d4f"; // 红色 - 已回收
|
||||||
|
default:
|
||||||
|
return "#d9d9d9"; // 灰色 - 未知状态
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRecycleText = (isRecycle?: number) => {
|
||||||
|
switch (isRecycle) {
|
||||||
|
case 0:
|
||||||
|
return "未回收";
|
||||||
|
case 1:
|
||||||
|
return "已回收";
|
||||||
|
default:
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popup
|
||||||
|
visible={visible}
|
||||||
|
onMaskClick={onClose}
|
||||||
|
position="bottom"
|
||||||
|
bodyStyle={{
|
||||||
|
height: "100vh",
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={style.accountModal}>
|
||||||
|
{/* 头部 */}
|
||||||
|
<div className={style.accountModalHeader}>
|
||||||
|
<h3 className={style.accountModalTitle}>{title}</h3>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
onClick={onClose}
|
||||||
|
className={style.accountModalClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<div className={style.searchBar}>
|
||||||
|
<div className={style.searchInputWrapper}>
|
||||||
|
<SearchOutlined className={style.searchIcon} />
|
||||||
|
<Input
|
||||||
|
placeholder="搜索分发记录(回车搜索)"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
onEnterPress={handleSearchEnter}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分发记录列表 */}
|
||||||
|
<div className={style.accountList}>
|
||||||
|
{loading ? (
|
||||||
|
<div className={style.accountLoading}>
|
||||||
|
<SpinLoading color="primary" />
|
||||||
|
<div className={style.accountLoadingText}>
|
||||||
|
正在加载分发记录...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : sendRecords.length > 0 ? (
|
||||||
|
sendRecords.map((record, index) => (
|
||||||
|
<div key={record.id || index} className={style.accountItem}>
|
||||||
|
<div className={style.accountAvatar}>
|
||||||
|
<Avatar
|
||||||
|
src={record.avatar}
|
||||||
|
style={{ "--size": "48px" }}
|
||||||
|
fallback={(record.nickname || record.wechatId || "账号")[0]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style.accountInfo}>
|
||||||
|
<div className={style.accountName}>
|
||||||
|
{record.nickname || record.wechatId || `账号${record.id}`}
|
||||||
|
</div>
|
||||||
|
<div className={style.accountWechatId}>
|
||||||
|
{record.wechatId || "未绑定微信号"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style.accountStatus}>
|
||||||
|
<span
|
||||||
|
className={style.statusDot}
|
||||||
|
style={{
|
||||||
|
backgroundColor: getRecycleColor(record.isRecycle),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className={style.statusText}>
|
||||||
|
{getRecycleText(record.isRecycle)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className={style.accountEmpty}>
|
||||||
|
<div className={style.accountEmptyText}>暂无分发记录</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部统计和分页 */}
|
||||||
|
<div className={style.accountModalFooter}>
|
||||||
|
<div className={style.accountStats}>
|
||||||
|
<span>共 {total} 条分发记录</span>
|
||||||
|
</div>
|
||||||
|
<div className={style.paginationContainer}>
|
||||||
|
<Pagination
|
||||||
|
current={currentPage}
|
||||||
|
pageSize={pageSize}
|
||||||
|
total={total}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
showSizeChanger={false}
|
||||||
|
showQuickJumper={false}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SendRcrodModal;
|
||||||
@@ -255,6 +255,9 @@
|
|||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountStats {
|
.accountStats {
|
||||||
@@ -263,6 +266,39 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchBar {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchInputWrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchIcon {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #999;
|
||||||
|
font-size: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchInputWrapper :global(.adm-input) {
|
||||||
|
padding-left: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paginationContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
// 设备列表弹窗样式
|
// 设备列表弹窗样式
|
||||||
.deviceModal {
|
.deviceModal {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import AccountListModal from "./components/AccountListModal";
|
import AccountListModal from "./components/AccountListModal";
|
||||||
import DeviceListModal from "./components/DeviceListModal";
|
import DeviceListModal from "./components/DeviceListModal";
|
||||||
import PoolListModal from "./components/PoolListModal";
|
import PoolListModal from "./components/PoolListModal";
|
||||||
|
import SendRcrodModal from "./components/SendRcrodModal";
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
const [accountModalVisible, setAccountModalVisible] = useState(false);
|
const [accountModalVisible, setAccountModalVisible] = useState(false);
|
||||||
const [deviceModalVisible, setDeviceModalVisible] = useState(false);
|
const [deviceModalVisible, setDeviceModalVisible] = useState(false);
|
||||||
const [poolModalVisible, setPoolModalVisible] = useState(false);
|
const [poolModalVisible, setPoolModalVisible] = useState(false);
|
||||||
|
const [sendRecordModalVisible, setSendRecordModalVisible] = useState(false);
|
||||||
const [currentRule, setCurrentRule] = useState<DistributionRule | null>(null);
|
const [currentRule, setCurrentRule] = useState<DistributionRule | null>(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -153,6 +155,12 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
setPoolModalVisible(true);
|
setPoolModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 显示分发统计弹窗
|
||||||
|
const showSendRecord = (item: DistributionRule) => {
|
||||||
|
setCurrentRule(item);
|
||||||
|
setSendRecordModalVisible(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)}>
|
||||||
@@ -287,7 +295,11 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
总流量池数量
|
总流量池数量
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.ruleStatsItem}>
|
<div
|
||||||
|
className={style.ruleStatsItem}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => showSendRecord(item)}
|
||||||
|
>
|
||||||
<span style={{ fontSize: 16, fontWeight: 600 }}>
|
<span style={{ fontSize: 16, fontWeight: 600 }}>
|
||||||
{item.config?.total?.totalUsers || 0}
|
{item.config?.total?.totalUsers || 0}
|
||||||
</span>
|
</span>
|
||||||
@@ -394,6 +406,14 @@ const TrafficDistributionList: React.FC = () => {
|
|||||||
ruleId={currentRule?.id}
|
ruleId={currentRule?.id}
|
||||||
ruleName={currentRule?.name}
|
ruleName={currentRule?.name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 分发统计弹窗 */}
|
||||||
|
<SendRcrodModal
|
||||||
|
visible={sendRecordModalVisible}
|
||||||
|
onClose={() => setSendRecordModalVisible(false)}
|
||||||
|
ruleId={currentRule?.id}
|
||||||
|
ruleName={currentRule?.name}
|
||||||
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user