diff --git a/nkebao/src/components/DeviceSelectionDialog/api.ts b/nkebao/src/components/DeviceSelectionDialog/api.ts deleted file mode 100644 index 1f28ce04e..000000000 --- a/nkebao/src/components/DeviceSelectionDialog/api.ts +++ /dev/null @@ -1,10 +0,0 @@ -import request from "@/api/request"; - -// 获取设备列表 -export function getDeviceList(params: { - page: number; - limit: number; - keyword?: string; -}) { - return request("/v1/devices", params, "GET"); -} diff --git a/nkebao/src/components/DeviceSelectionDialog/index.module.scss b/nkebao/src/components/DeviceSelectionDialog/index.module.scss deleted file mode 100644 index e0961f10c..000000000 --- a/nkebao/src/components/DeviceSelectionDialog/index.module.scss +++ /dev/null @@ -1,197 +0,0 @@ -.popupContainer { - display: flex; - flex-direction: column; - height: 100vh; - background: #fff; -} -.popupHeader { - padding: 16px; - border-bottom: 1px solid #f0f0f0; -} -.popupTitle { - font-size: 18px; - font-weight: 600; - text-align: center; -} -.popupSearchRow { - display: flex; - align-items: center; - gap: 16px; - padding: 16px; -} -.popupSearchInputWrap { - position: relative; - flex: 1; -} -.inputIcon { - position: absolute; - left: 12px; - top: 50%; - transform: translateY(-50%); - color: #bdbdbd; - z-index: 10; - font-size: 16px; -} -.popupSearchInput { - padding-left: 36px !important; - border-radius: 12px !important; - height: 44px; - font-size: 15px; - border: 1px solid #e5e6eb !important; - background: #f8f9fa; -} -.statusSelect { - width: 128px; - height: 40px; - border-radius: 8px; - border: 1px solid #e5e6eb; - font-size: 14px; - padding: 0 12px; - background: #fff; -} -.loadingIcon { - animation: spin 1s linear infinite; - font-size: 16px; -} -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} -.deviceList { - flex: 1; - overflow-y: auto; -} -.deviceListInner { - display: flex; - flex-direction: column; - gap: 12px; - padding: 16px; -} -.deviceItem { - display: flex; - align-items: flex-start; - gap: 12px; - padding: 16px; - border-radius: 12px; - border: 1px solid #f0f0f0; - background: #fff; - cursor: pointer; - transition: background 0.2s; - &:hover { - background: #f5f6fa; - } -} -.deviceCheckbox { - margin-top: 4px; -} -.deviceInfo { - flex: 1; -} -.deviceInfoRow { - display: flex; - align-items: center; - justify-content: space-between; -} -.deviceName { - font-weight: 500; - font-size: 16px; - color: #222; -} -.statusOnline { - padding: 4px 8px; - border-radius: 12px; - background: #52c41a; - color: #fff; - font-size: 12px; - display: flex; - align-items: center; - justify-content: center; -} -.statusOffline { - padding: 4px 8px; - border-radius: 12px; - background: #e5e6eb; - color: #888; - font-size: 12px; - display: flex; - align-items: center; - justify-content: center; -} -.deviceInfoDetail { - font-size: 13px; - color: #888; - margin-top: 4px; -} -.usedInPlans { - font-size: 13px; - color: #fa8c16; - margin-top: 4px; -} -.loadingBox { - display: flex; - align-items: center; - justify-content: center; - height: 100%; -} -.loadingText { - color: #888; - font-size: 15px; -} -.emptyBox { - display: flex; - align-items: center; - justify-content: center; - height: 100%; -} -.emptyText { - color: #888; - font-size: 15px; -} -.popupFooter { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px; - border-top: 1px solid #f0f0f0; - background: #fff; -} -.selectedCount { - font-size: 14px; - color: #888; -} -.footerBtnGroup { - display: flex; - gap: 12px; -} -.refreshBtn { - width: 36px; - height: 36px; -} -.paginationRow { - border-top: 1px solid #f0f0f0; - padding: 16px; - display: flex; - align-items: center; - justify-content: space-between; - background: #fff; -} -.totalCount { - font-size: 14px; - color: #888; -} -.paginationControls { - display: flex; - align-items: center; - gap: 8px; -} -.pageBtn { - padding: 0 8px; - height: 32px; - min-width: 32px; - border-radius: 16px; -} -.pageInfo { - font-size: 14px; - color: #222; - margin: 0 8px; -} diff --git a/nkebao/src/components/DeviceSelectionDialog/index.tsx b/nkebao/src/components/DeviceSelectionDialog/index.tsx deleted file mode 100644 index 588d774a8..000000000 --- a/nkebao/src/components/DeviceSelectionDialog/index.tsx +++ /dev/null @@ -1,258 +0,0 @@ -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"; - -interface Device { - id: string; - name: string; - imei: string; - wxid: string; - status: "online" | "offline"; - usedInPlans: number; - nickname: string; -} - -interface DeviceSelectionDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - selectedDevices: string[]; - onSelect: (devices: string[]) => void; -} - -export function DeviceSelectionDialog({ - open, - onOpenChange, - selectedDevices, - onSelect, -}: DeviceSelectionDialogProps) { - const [searchQuery, setSearchQuery] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); - const [loading, setLoading] = useState(false); - const [devices, setDevices] = useState([]); - const [currentPage, setCurrentPage] = useState(1); // 新增 - const [total, setTotal] = useState(0); // 新增 - const pageSize = 20; // 每页条数 - - // 获取设备列表,支持keyword和分页 - const fetchDevices = useCallback( - async (keyword: string = "", page: number = 1) => { - setLoading(true); - try { - const response = await getDeviceList({ - page, - limit: pageSize, - keyword: keyword.trim() || undefined, - }); - if (response && Array.isArray(response.list)) { - const convertedDevices: Device[] = response.list.map( - (serverDevice: any) => ({ - id: serverDevice.id.toString(), - name: serverDevice.memo || `设备 ${serverDevice.id}`, - imei: serverDevice.imei, - wxid: serverDevice.wechatId || "", - status: serverDevice.alive === 1 ? "online" : "offline", - usedInPlans: 0, - nickname: serverDevice.nickname || "", - }) - ); - setDevices(convertedDevices); - setTotal(response.total || 0); - } - } catch (error) { - console.error("获取设备列表失败:", error); - Toast.show({ - content: "获取设备列表失败,请检查网络连接", - position: "top", - }); - } finally { - setLoading(false); - } - }, - [] - ); - - // 打开弹窗时获取第一页 - useEffect(() => { - if (open) { - setCurrentPage(1); - fetchDevices("", 1); - } - }, [open, fetchDevices]); - - // 搜索防抖 - useEffect(() => { - if (!open) return; - const timer = setTimeout(() => { - setCurrentPage(1); - fetchDevices(searchQuery, 1); - }, 500); - return () => clearTimeout(timer); - }, [searchQuery, open, fetchDevices]); - - // 翻页时重新请求 - useEffect(() => { - if (!open) 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 handleDeviceSelect = (deviceId: string) => { - if (selectedDevices.includes(deviceId)) { - onSelect(selectedDevices.filter((id) => id !== deviceId)); - } else { - onSelect([...selectedDevices, deviceId]); - } - }; - - const totalPages = Math.max(1, Math.ceil(total / pageSize)); - - return ( - onOpenChange(false)} - position="bottom" - bodyStyle={{ height: "100vh" }} - > -
-
-
选择设备
-
-
-
- - setSearchQuery(val)} - className={style.popupSearchInput} - /> -
- - -
-
- {loading ? ( -
-
加载中...
-
- ) : filteredDevices.length === 0 ? ( -
-
暂无数据
-
- ) : ( -
- {filteredDevices.map((device) => ( - - ))} -
- )} -
- {/* 分页栏 */} -
-
总计 {total} 个设备
-
- - - {currentPage} / {totalPages} - - -
-
-
-
- 已选择 {selectedDevices.length} 个设备 -
-
- - -
-
-
-
- ); -} diff --git a/nkebao/src/components/FriendSelection/index.module.scss b/nkebao/src/components/FriendSelection/index.module.scss index 0c82db961..663353e8d 100644 --- a/nkebao/src/components/FriendSelection/index.module.scss +++ b/nkebao/src/components/FriendSelection/index.module.scss @@ -205,13 +205,21 @@ } .popupFooter { - border-top: 1px solid #f0f0f0; - padding: 16px; display: flex; align-items: center; justify-content: space-between; + padding: 16px; + border-top: 1px solid #f0f0f0; background: #fff; } +.selectedCount { + font-size: 14px; + color: #888; +} +.footerBtnGroup { + display: flex; + gap: 12px; +} .cancelBtn { padding: 0 24px; border-radius: 24px; diff --git a/nkebao/src/components/FriendSelection/index.tsx b/nkebao/src/components/FriendSelection/index.tsx index d67aa0ff8..13090569c 100644 --- a/nkebao/src/components/FriendSelection/index.tsx +++ b/nkebao/src/components/FriendSelection/index.tsx @@ -1,11 +1,7 @@ import React, { useState, useEffect } from "react"; -import { - SearchOutlined, - CloseOutlined, - LeftOutlined, - RightOutlined, -} from "@ant-design/icons"; -import { Input, Button, Popup, Toast } from "antd-mobile"; +import { SearchOutlined, DeleteOutlined } from "@ant-design/icons"; +import { Popup, Toast } from "antd-mobile"; +import { Button, Input } from "antd"; import { getFriendList } from "./api"; import style from "./index.module.scss"; @@ -29,6 +25,10 @@ interface FriendSelectionProps { className?: string; visible?: boolean; // 新增 onVisibleChange?: (visible: boolean) => void; // 新增 + selectedListMaxHeight?: number; + showInput?: boolean; + showSelectedList?: boolean; + readonly?: boolean; } export default function FriendSelection({ @@ -41,6 +41,10 @@ export default function FriendSelection({ className = "", visible, onVisibleChange, + selectedListMaxHeight = 300, + showInput = true, + showSelectedList = true, + readonly = false, }: FriendSelectionProps) { const [popupVisible, setPopupVisible] = useState(false); const [friends, setFriends] = useState([]); @@ -59,6 +63,7 @@ export default function FriendSelection({ // 打开弹窗并请求第一页好友 const openPopup = () => { + if (readonly) return; setCurrentPage(1); setSearchQuery(""); setRealVisible(true); @@ -152,6 +157,17 @@ export default function FriendSelection({ return `已选择 ${selectedFriends.length} 个好友`; }; + // 获取已选好友详细信息 + const selectedFriendObjs = selectedFriends + .map((id) => friends.find((f) => f.id === id)) + .filter(Boolean) as WechatFriend[]; + + // 删除已选好友 + const handleRemoveFriend = (id: string) => { + if (readonly) return; + onSelect(selectedFriends.filter((f) => f !== id)); + }; + const handleConfirm = () => { setPopupVisible(false); }; @@ -166,35 +182,85 @@ export default function FriendSelection({ return ( <> {/* 输入框 */} -
- - - - - - -
- - {/* 微信好友选择弹窗 */} + {showInput && ( +
+ } + allowClear={!readonly} + size="large" + readOnly={readonly} + disabled={readonly} + style={ + readonly ? { background: "#f5f5f5", cursor: "not-allowed" } : {} + } + /> +
+ )} + {/* 已选好友列表窗口 */} + {showSelectedList && selectedFriendObjs.length > 0 && ( +
+ {selectedFriendObjs.map((friend) => ( +
+
+ {friend.nickname || friend.wechatId || friend.id} +
+ {!readonly && ( +
+ ))} +
+ )} + {/* 弹窗 */} setRealVisible(false)} position="bottom" bodyStyle={{ height: "100vh" }} @@ -206,23 +272,33 @@ export default function FriendSelection({ setSearchQuery(val)} - className={style.searchInput} + onChange={(e) => setSearchQuery(e.target.value)} + disabled={readonly} + prefix={} + allowClear + size="large" /> - - {searchQuery && ( + {searchQuery && !readonly && ( + style={{ + color: "#ff4d4f", + border: "none", + background: "none", + minWidth: 24, + height: 24, + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + /> )} -
{loading ? (
@@ -234,7 +310,7 @@ export default function FriendSelection({