Merge branch 'yongpxu-dev' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into yongpxu-dev
This commit is contained in:
@@ -31,3 +31,13 @@ export function deleteDistributionRule(id: number): Promise<any> {
|
||||
export function fetchDistributionRuleDetail(id: number): Promise<any> {
|
||||
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) 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 || searchKeyword === "") return;
|
||||
setCurrentPage(1);
|
||||
fetchSendRecords(1, searchKeyword);
|
||||
}, [searchKeyword]);
|
||||
|
||||
// 页码变化
|
||||
useEffect(() => {
|
||||
if (!visible || !ruleId || currentPage === 1) return;
|
||||
fetchSendRecords(currentPage, searchKeyword);
|
||||
}, [currentPage]);
|
||||
|
||||
// 处理页码变化
|
||||
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;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.accountStats {
|
||||
@@ -263,6 +266,39 @@
|
||||
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 {
|
||||
height: 100%;
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import AccountListModal from "./components/AccountListModal";
|
||||
import DeviceListModal from "./components/DeviceListModal";
|
||||
import PoolListModal from "./components/PoolListModal";
|
||||
import SendRcrodModal from "./components/SendRcrodModal";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
@@ -57,6 +58,7 @@ const TrafficDistributionList: React.FC = () => {
|
||||
const [accountModalVisible, setAccountModalVisible] = useState(false);
|
||||
const [deviceModalVisible, setDeviceModalVisible] = useState(false);
|
||||
const [poolModalVisible, setPoolModalVisible] = useState(false);
|
||||
const [sendRecordModalVisible, setSendRecordModalVisible] = useState(false);
|
||||
const [currentRule, setCurrentRule] = useState<DistributionRule | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -153,6 +155,12 @@ const TrafficDistributionList: React.FC = () => {
|
||||
setPoolModalVisible(true);
|
||||
};
|
||||
|
||||
// 显示分发统计弹窗
|
||||
const showSendRecord = (item: DistributionRule) => {
|
||||
setCurrentRule(item);
|
||||
setSendRecordModalVisible(true);
|
||||
};
|
||||
|
||||
const renderCard = (item: DistributionRule) => {
|
||||
const menu = (
|
||||
<Menu onClick={({ key }) => handleMenuClick(key, item)}>
|
||||
@@ -287,7 +295,11 @@ const TrafficDistributionList: React.FC = () => {
|
||||
总流量池数量
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.ruleStatsItem}>
|
||||
<div
|
||||
className={style.ruleStatsItem}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => showSendRecord(item)}
|
||||
>
|
||||
<span style={{ fontSize: 16, fontWeight: 600 }}>
|
||||
{item.config?.total?.totalUsers || 0}
|
||||
</span>
|
||||
@@ -394,6 +406,14 @@ const TrafficDistributionList: React.FC = () => {
|
||||
ruleId={currentRule?.id}
|
||||
ruleName={currentRule?.name}
|
||||
/>
|
||||
|
||||
{/* 分发统计弹窗 */}
|
||||
<SendRcrodModal
|
||||
visible={sendRecordModalVisible}
|
||||
onClose={() => setSendRecordModalVisible(false)}
|
||||
ruleId={currentRule?.id}
|
||||
ruleName={currentRule?.name}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user