288 lines
9.6 KiB
TypeScript
288 lines
9.6 KiB
TypeScript
import React, { useState, useEffect, useCallback } from "react";
|
||
import { Checkbox, Popup } from "antd-mobile";
|
||
import { getDeviceList } from "./api";
|
||
import style from "./index.module.scss";
|
||
import Layout from "@/components/Layout/Layout";
|
||
import PopupHeader from "@/components/PopuLayout/header";
|
||
import PopupFooter from "@/components/PopuLayout/footer";
|
||
import { DeviceSelectionItem } from "./data";
|
||
|
||
interface SelectionPopupProps {
|
||
visible: boolean;
|
||
onClose: () => void;
|
||
selectedOptions: DeviceSelectionItem[];
|
||
onSelect: (devices: DeviceSelectionItem[]) => void;
|
||
singleSelect?: boolean;
|
||
}
|
||
|
||
const PAGE_SIZE = 20;
|
||
|
||
const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
||
visible,
|
||
onClose,
|
||
selectedOptions,
|
||
onSelect,
|
||
singleSelect = false,
|
||
}) => {
|
||
// 设备数据
|
||
const [devices, setDevices] = useState<DeviceSelectionItem[]>([]);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
const [statusFilter, setStatusFilter] = useState("all");
|
||
const [loading, setLoading] = useState(false);
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const [total, setTotal] = useState(0);
|
||
const [tempSelectedOptions, setTempSelectedOptions] = useState<
|
||
DeviceSelectionItem[]
|
||
>([]);
|
||
|
||
// 获取设备列表,支持keyword和分页
|
||
const fetchDevices = useCallback(
|
||
async (keyword: string = "", page: number = 1) => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await getDeviceList({
|
||
page,
|
||
limit: PAGE_SIZE,
|
||
keyword: keyword.trim() || undefined,
|
||
});
|
||
if (res && Array.isArray(res.list)) {
|
||
setDevices(
|
||
res.list.map((d: any) => ({
|
||
id: d.id?.toString() || "",
|
||
memo: d.memo || d.imei || "",
|
||
imei: d.imei || "",
|
||
wechatId: d.wechatId || "",
|
||
status: d.alive === 1 ? "online" : "offline",
|
||
wxid: d.wechatId || "",
|
||
nickname: d.nickname || "",
|
||
usedInPlans: d.usedInPlans || 0,
|
||
avatar: d.avatar || "",
|
||
totalFriend: d.totalFriend || 0,
|
||
})),
|
||
);
|
||
setTotal(res.total || 0);
|
||
}
|
||
} catch (error) {
|
||
console.error("获取设备列表失败:", error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[],
|
||
);
|
||
|
||
// 打开弹窗时获取第一页
|
||
useEffect(() => {
|
||
if (visible) {
|
||
setSearchQuery("");
|
||
setCurrentPage(1);
|
||
// 复制一份selectedOptions到临时变量
|
||
setTempSelectedOptions([...selectedOptions]);
|
||
fetchDevices("", 1);
|
||
}
|
||
}, [visible, fetchDevices, selectedOptions]);
|
||
|
||
// 搜索防抖
|
||
useEffect(() => {
|
||
if (!visible) return;
|
||
const timer = setTimeout(() => {
|
||
setCurrentPage(1);
|
||
fetchDevices(searchQuery, 1);
|
||
}, 500);
|
||
return () => clearTimeout(timer);
|
||
}, [searchQuery, visible, fetchDevices]);
|
||
|
||
// 翻页时重新请求
|
||
useEffect(() => {
|
||
if (!visible) return;
|
||
fetchDevices(searchQuery, currentPage);
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [currentPage]);
|
||
|
||
// 过滤设备(只保留状态过滤)
|
||
const filteredDevices = devices.filter(device => {
|
||
const matchesStatus =
|
||
statusFilter === "all" ||
|
||
(statusFilter === "online" && device.status === "online") ||
|
||
(statusFilter === "offline" && device.status === "offline");
|
||
return matchesStatus;
|
||
});
|
||
|
||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||
|
||
// 处理设备选择
|
||
const handleDeviceToggle = (device: DeviceSelectionItem) => {
|
||
if (singleSelect) {
|
||
// 单选模式:如果已选中,则取消选择;否则替换为当前设备
|
||
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
||
setTempSelectedOptions([]);
|
||
} else {
|
||
setTempSelectedOptions([device]);
|
||
}
|
||
} else {
|
||
// 多选模式:原有的逻辑
|
||
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
||
setTempSelectedOptions(
|
||
tempSelectedOptions.filter(v => v.id !== device.id),
|
||
);
|
||
} else {
|
||
const newSelectedOptions = [...tempSelectedOptions, device];
|
||
setTempSelectedOptions(newSelectedOptions);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 全选当前页
|
||
const handleSelectAllCurrentPage = (checked: boolean) => {
|
||
if (checked) {
|
||
// 全选:添加当前页面所有未选中的设备
|
||
const currentPageDevices = filteredDevices.filter(
|
||
device => !tempSelectedOptions.some(d => d.id === device.id),
|
||
);
|
||
setTempSelectedOptions(prev => [...prev, ...currentPageDevices]);
|
||
} else {
|
||
// 取消全选:移除当前页面的所有设备
|
||
const currentPageDeviceIds = filteredDevices.map(d => d.id);
|
||
setTempSelectedOptions(prev =>
|
||
prev.filter(d => !currentPageDeviceIds.includes(d.id)),
|
||
);
|
||
}
|
||
};
|
||
|
||
// 检查当前页是否全选
|
||
const isCurrentPageAllSelected =
|
||
filteredDevices.length > 0 &&
|
||
filteredDevices.every(device =>
|
||
tempSelectedOptions.some(d => d.id === device.id),
|
||
);
|
||
|
||
return (
|
||
<Popup
|
||
visible={visible}
|
||
onMaskClick={onClose}
|
||
position="bottom"
|
||
bodyStyle={{ height: "100vh" }}
|
||
closeOnMaskClick={false}
|
||
>
|
||
<Layout
|
||
header={
|
||
<PopupHeader
|
||
title="选择设备"
|
||
searchQuery={searchQuery}
|
||
setSearchQuery={setSearchQuery}
|
||
searchPlaceholder="搜索设备IMEI/备注/微信号"
|
||
loading={loading}
|
||
onRefresh={() => fetchDevices(searchQuery, currentPage)}
|
||
showTabs={true}
|
||
tabsConfig={{
|
||
activeKey: statusFilter,
|
||
onChange: setStatusFilter,
|
||
tabs: [
|
||
{ title: "全部", key: "all" },
|
||
{ title: "在线", key: "online" },
|
||
{ title: "离线", key: "offline" },
|
||
],
|
||
}}
|
||
/>
|
||
}
|
||
footer={
|
||
<PopupFooter
|
||
currentPage={currentPage}
|
||
totalPages={totalPages}
|
||
loading={loading}
|
||
selectedCount={tempSelectedOptions.length}
|
||
singleSelect={singleSelect}
|
||
onPageChange={setCurrentPage}
|
||
onCancel={onClose}
|
||
onConfirm={() => {
|
||
// 用户点击确认时,才更新实际的selectedOptions
|
||
onSelect(tempSelectedOptions);
|
||
onClose();
|
||
}}
|
||
isAllSelected={isCurrentPageAllSelected}
|
||
onSelectAll={singleSelect ? undefined : handleSelectAllCurrentPage}
|
||
/>
|
||
}
|
||
>
|
||
<div className={style.deviceList}>
|
||
{loading ? (
|
||
<div className={style.loadingBox}>
|
||
<div className={style.loadingText}>加载中...</div>
|
||
</div>
|
||
) : (
|
||
<div className={style.deviceListInner}>
|
||
{filteredDevices.map(device => (
|
||
<div key={device.id} className={style.deviceItem}>
|
||
{/* 顶部行:选择框和IMEI */}
|
||
<div className={style.headerRow}>
|
||
<div className={style.checkboxContainer}>
|
||
<Checkbox
|
||
checked={tempSelectedOptions.some(
|
||
v => v.id === device.id,
|
||
)}
|
||
onChange={() => handleDeviceToggle(device)}
|
||
className={style.deviceCheckbox}
|
||
/>
|
||
</div>
|
||
<span className={style.imeiText}>
|
||
IMEI: {device.imei?.toUpperCase()}
|
||
</span>
|
||
</div>
|
||
|
||
{/* 主要内容区域:头像和详细信息 */}
|
||
<div className={style.mainContent}>
|
||
{/* 头像 */}
|
||
<div className={style.deviceAvatar}>
|
||
{device.avatar ? (
|
||
<img src={device.avatar} alt="头像" />
|
||
) : (
|
||
<span className={style.avatarText}>
|
||
{(device.memo || device.wechatId || "设")[0]}
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{/* 设备信息 */}
|
||
<div className={style.deviceContent}>
|
||
<div className={style.deviceInfoRow}>
|
||
<span className={style.deviceName}>{device.memo}</span>
|
||
<div
|
||
className={
|
||
device.status === "online"
|
||
? style.statusOnline
|
||
: style.statusOffline
|
||
}
|
||
>
|
||
{device.status === "online" ? "在线" : "离线"}
|
||
</div>
|
||
</div>
|
||
<div className={style.deviceInfoDetail}>
|
||
<div className={style.infoItem}>
|
||
<span className={style.infoLabel}>微信号:</span>
|
||
<span className={style.infoValue}>
|
||
{device.wechatId}
|
||
</span>
|
||
</div>
|
||
<div className={style.infoItem}>
|
||
<span className={style.infoLabel}>好友数:</span>
|
||
<span
|
||
className={`${style.infoValue} ${style.friendCount}`}
|
||
>
|
||
{device.totalFriend ?? "-"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</Layout>
|
||
</Popup>
|
||
);
|
||
};
|
||
|
||
export default SelectionPopup;
|