diff --git a/nkebao/.env.development b/nkebao/.env.development index fe189d22..da6a111b 100644 --- a/nkebao/.env.development +++ b/nkebao/.env.development @@ -1,4 +1,4 @@ # 基础环境变量示例 -VITE_API_BASE_URL=https://ckbapi.quwanzhi.com +VITE_API_BASE_URL=http://www.yishi.com VITE_APP_TITLE=Nkebao Base diff --git a/nkebao/src/components/DeviceSelection/index.tsx b/nkebao/src/components/DeviceSelection/index.tsx index b59e8104..126d43fc 100644 --- a/nkebao/src/components/DeviceSelection/index.tsx +++ b/nkebao/src/components/DeviceSelection/index.tsx @@ -1,248 +1,369 @@ -import React, { useState, useEffect } from "react"; -import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; -import { Checkbox, Popup, Toast } from "antd-mobile"; -import { Input, Button } from "antd"; -import { getDeviceList } from "./api"; -import style from "./index.module.scss"; - -// 设备选择项接口 -interface DeviceSelectionItem { - id: string; - name: string; - imei: string; - wechatId: string; - status: "online" | "offline"; -} - -// 组件属性接口 -interface DeviceSelectionProps { - selectedDevices: string[]; - onSelect: (devices: string[]) => void; - placeholder?: string; - className?: string; -} - -export default function DeviceSelection({ - selectedDevices, - onSelect, - placeholder = "选择设备", - className = "", -}: DeviceSelectionProps) { - const [popupVisible, setPopupVisible] = useState(false); - const [devices, setDevices] = useState([]); - 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 pageSize = 20; // 每页条数 - - // 获取设备列表,支持keyword和分页 - const fetchDevices = async (keyword: string = "", page: number = 1) => { - setLoading(true); - try { - const res = await getDeviceList({ - page, - limit: pageSize, - keyword: keyword.trim() || undefined, - }); - if (res && Array.isArray(res.list)) { - setDevices( - res.list.map((d: any) => ({ - id: d.id?.toString() || "", - name: d.memo || d.imei || "", - imei: d.imei || "", - wechatId: d.wechatId || "", - status: d.alive === 1 ? "online" : "offline", - })) - ); - setTotal(res.total || 0); - } - } catch (error) { - console.error("获取设备列表失败:", error); - Toast.show({ content: "获取设备列表失败", position: "top" }); - } finally { - setLoading(false); - } - }; - - // 打开弹窗时获取第一页 - const openPopup = () => { - setSearchQuery(""); - setCurrentPage(1); - setPopupVisible(true); - fetchDevices("", 1); - }; - - // 搜索防抖 - useEffect(() => { - if (!popupVisible) return; - const timer = setTimeout(() => { - setCurrentPage(1); - fetchDevices(searchQuery, 1); - }, 500); - return () => clearTimeout(timer); - }, [searchQuery, popupVisible]); - - // 翻页时重新请求 - useEffect(() => { - if (!popupVisible) 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 / pageSize)); - - // 处理设备选择 - const handleDeviceToggle = (deviceId: string) => { - if (selectedDevices.includes(deviceId)) { - onSelect(selectedDevices.filter((id) => id !== deviceId)); - } else { - onSelect([...selectedDevices, deviceId]); - } - }; - - // 获取显示文本 - const getDisplayText = () => { - if (selectedDevices.length === 0) return ""; - return `已选择 ${selectedDevices.length} 个设备`; - }; - - return ( - <> - {/* 输入框 */} -
- } - allowClear - size="large" - /> -
- - {/* 设备选择弹窗 */} - setPopupVisible(false)} - position="bottom" - bodyStyle={{ height: "100vh" }} - > -
-
-
选择设备
-
-
-
- - setSearchQuery(val)} - className={style.popupSearchInput} - /> -
- -
-
- {loading ? ( -
-
加载中...
-
- ) : ( -
- {filteredDevices.map((device) => ( - - ))} -
- )} -
- {/* 分页栏 */} -
-
总计 {total} 个设备
-
- - - {currentPage} / {totalPages} - - -
-
-
-
- 已选择 {selectedDevices.length} 个设备 -
-
- - -
-
-
-
- - ); -} +import React, { useState, useEffect, useCallback } from "react"; +import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Checkbox, Popup, Toast } from "antd-mobile"; +import { Input, Button } from "antd"; +import { getDeviceList } from "./api"; +import style from "./index.module.scss"; +import { DeleteOutlined } from "@ant-design/icons"; + +// 设备选择项接口 +interface DeviceSelectionItem { + id: string; + name: string; + imei: string; + wechatId: string; + status: "online" | "offline"; + wxid?: string; + nickname?: string; + usedInPlans?: number; +} + +// 组件属性接口 +interface DeviceSelectionProps { + selectedDevices: string[]; + onSelect: (devices: string[]) => void; + placeholder?: string; + className?: string; + mode?: "input" | "dialog"; // 新增,默认input + open?: boolean; // 仅mode=dialog时生效 + onOpenChange?: (open: boolean) => void; // 仅mode=dialog时生效 + selectedListMaxHeight?: number; // 新增,已选列表最大高度,默认500 + showInput?: boolean; // 新增 + showSelectedList?: boolean; // 新增 +} + +const PAGE_SIZE = 20; + +const DeviceSelection: React.FC = ({ + selectedDevices, + onSelect, + placeholder = "选择设备", + className = "", + mode = "input", + open, + onOpenChange, + selectedListMaxHeight = 300, // 默认300 + showInput = true, + showSelectedList = true, +}) => { + // 弹窗控制 + const [popupVisible, setPopupVisible] = useState(false); + const isDialog = mode === "dialog"; + const realVisible = isDialog ? !!open : popupVisible; + const setRealVisible = (v: boolean) => { + if (isDialog && onOpenChange) onOpenChange(v); + if (!isDialog) setPopupVisible(v); + }; + + // 设备数据 + const [devices, setDevices] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + const [loading, setLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [total, setTotal] = useState(0); + + // 获取设备列表,支持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() || "", + name: 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, + })) + ); + setTotal(res.total || 0); + } + } catch (error) { + console.error("获取设备列表失败:", error); + } finally { + setLoading(false); + } + }, + [] + ); + + // 打开弹窗时获取第一页 + const openPopup = () => { + setSearchQuery(""); + setCurrentPage(1); + setRealVisible(true); + fetchDevices("", 1); + }; + + // 搜索防抖 + useEffect(() => { + if (!realVisible) return; + const timer = setTimeout(() => { + setCurrentPage(1); + fetchDevices(searchQuery, 1); + }, 500); + return () => clearTimeout(timer); + }, [searchQuery, realVisible, fetchDevices]); + + // 翻页时重新请求 + useEffect(() => { + if (!realVisible) 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 = (deviceId: string) => { + if (selectedDevices.includes(deviceId)) { + onSelect(selectedDevices.filter((id) => id !== deviceId)); + } else { + onSelect([...selectedDevices, deviceId]); + } + }; + + // 获取显示文本 + const getDisplayText = () => { + if (selectedDevices.length === 0) return ""; + return `已选择 ${selectedDevices.length} 个设备`; + }; + + // 获取已选设备详细信息 + const selectedDeviceObjs = selectedDevices + .map((id) => devices.find((d) => d.id === id)) + .filter(Boolean) as DeviceSelectionItem[]; + + // 删除已选设备 + const handleRemoveDevice = (id: string) => { + onSelect(selectedDevices.filter((d) => d !== id)); + }; + + // 弹窗内容 + const popupContent = ( +
+
+
选择设备
+
+
+
+ + setSearchQuery(val)} + className={style.popupSearchInput} + /> +
+ + +
+
+ {loading ? ( +
+
加载中...
+
+ ) : ( +
+ {filteredDevices.map((device) => ( + + ))} +
+ )} +
+ {/* 分页栏 */} +
+
总计 {total} 个设备
+
+ + + {currentPage} / {totalPages} + + +
+
+
+
+ 已选择 {selectedDevices.length} 个设备 +
+
+ + +
+
+
+ ); + + return ( + <> + {/* mode=input 显示输入框,mode=dialog不显示 */} + {mode === "input" && showInput && ( +
+ } + allowClear + size="large" + /> +
+ )} + {/* 已选设备列表窗口 */} + {mode === "input" && + showSelectedList && + selectedDeviceObjs.length > 0 && ( +
+ {selectedDeviceObjs.map((device) => ( +
+
+ {device.name} +
+
+ ))} +
+ )} + {/* 弹窗 */} + setRealVisible(false)} + position="bottom" + bodyStyle={{ height: "100vh" }} + > + {popupContent} + + + ); +}; + +export default DeviceSelection; diff --git a/nkebao/src/components/DeviceSelection/selectionPopup.tsx b/nkebao/src/components/DeviceSelection/selectionPopup.tsx new file mode 100644 index 00000000..8f77eabc --- /dev/null +++ b/nkebao/src/components/DeviceSelection/selectionPopup.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Input, Button, Checkbox, Popup } from "antd-mobile"; +import style from "./index.module.scss"; + +interface DeviceSelectionItem { + id: string; + name: string; + imei: string; + wechatId: string; + status: "online" | "offline"; + wxid?: string; + nickname?: string; + usedInPlans?: number; +} + +interface SelectionPopupProps { + visible: boolean; + onClose: () => void; + selectedDevices: string[]; + onSelect: (devices: string[]) => void; + devices: DeviceSelectionItem[]; + loading: boolean; + searchQuery: string; + setSearchQuery: (v: string) => void; + statusFilter: string; + setStatusFilter: (v: string) => void; + onRefresh: () => void; + filteredDevices: DeviceSelectionItem[]; + total: number; + currentPage: number; + totalPages: number; + setCurrentPage: (v: number) => void; + onCancel: () => void; + onConfirm: () => void; +} + +const SelectionPopup: React.FC = ({ + visible, + onClose, + selectedDevices, + onSelect, + devices, + loading, + searchQuery, + setSearchQuery, + statusFilter, + setStatusFilter, + onRefresh, + filteredDevices, + total, + currentPage, + totalPages, + setCurrentPage, + onCancel, + onConfirm, +}) => { + // 处理设备选择 + const handleDeviceToggle = (deviceId: string) => { + if (selectedDevices.includes(deviceId)) { + onSelect(selectedDevices.filter((id) => id !== deviceId)); + } else { + onSelect([...selectedDevices, deviceId]); + } + }; + + return ( + {}} + position="bottom" + bodyStyle={{ height: "100vh" }} + closeOnMaskClick={false} + > +
+
+
选择设备
+
+
+
+ + +
+ + +
+
+ {loading ? ( +
+
加载中...
+
+ ) : ( +
+ {filteredDevices.map((device) => ( + + ))} +
+ )} +
+ {/* 分页栏 */} +
+
总计 {total} 个设备
+
+ + + {currentPage} / {totalPages} + + +
+
+
+
+ 已选择 {selectedDevices.length} 个设备 +
+
+ + +
+
+
+
+ ); +}; + +export default SelectionPopup;