From d785f11aff3239831e88661c1581644f26e3c0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 8 Aug 2025 13:56:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E5=A5=BD?= =?UTF-8?q?=E5=8F=8B=E9=80=89=E6=8B=A9=E6=9E=84=E5=BB=BA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FriendSelection/index.module.scss | 15 + .../src/components/FriendSelection/index.tsx | 273 +++--------------- .../FriendSelection/selectionPopup.tsx | 213 ++++++++++++++ nkebao/src/pages/mobile/test/select.tsx | 6 +- 4 files changed, 278 insertions(+), 229 deletions(-) create mode 100644 nkebao/src/components/FriendSelection/selectionPopup.tsx diff --git a/nkebao/src/components/FriendSelection/index.module.scss b/nkebao/src/components/FriendSelection/index.module.scss index 51eb1af5..f450dde2 100644 --- a/nkebao/src/components/FriendSelection/index.module.scss +++ b/nkebao/src/components/FriendSelection/index.module.scss @@ -1,6 +1,21 @@ .inputWrapper { position: relative; } +.selectedListRow { + padding: 8px; + border-bottom: 1px solid #f0f0f0; + font-size: 14px; +} +.selectedListRowContent { + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.selectedListRowContentText { + flex: 1; +} .inputIcon { position: absolute; left: 12px; diff --git a/nkebao/src/components/FriendSelection/index.tsx b/nkebao/src/components/FriendSelection/index.tsx index 51fcc382..efdcad64 100644 --- a/nkebao/src/components/FriendSelection/index.tsx +++ b/nkebao/src/components/FriendSelection/index.tsx @@ -1,13 +1,10 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { SearchOutlined, DeleteOutlined } from "@ant-design/icons"; -import { Popup } from "antd-mobile"; import { Button, Input } from "antd"; -import { getFriendList } from "./api"; +import { Avatar } from "antd-mobile"; 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 { FriendSelectionProps, FriendSelectionItem } from "./data"; +import { FriendSelectionProps } from "./data"; +import SelectionPopup from "./selectionPopup"; export default function FriendSelection({ selectedOptions, @@ -25,12 +22,7 @@ export default function FriendSelection({ onConfirm, }: FriendSelectionProps) { const [popupVisible, setPopupVisible] = useState(false); - const [friends, setFriends] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [currentPage, setCurrentPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); - const [totalFriends, setTotalFriends] = useState(0); - const [loading, setLoading] = useState(false); + // 内部弹窗交给 selectionPopup 处理 // 受控弹窗逻辑 const realVisible = visible !== undefined ? visible : popupVisible; @@ -39,75 +31,10 @@ export default function FriendSelection({ if (visible === undefined) setPopupVisible(v); }; - // 打开弹窗并请求第一页好友 + // 打开弹窗 const openPopup = () => { if (readonly) return; - setCurrentPage(1); - setSearchQuery(""); setRealVisible(true); - fetchFriends(1, ""); - }; - - // 当页码变化时,拉取对应页数据(弹窗已打开时) - useEffect(() => { - if (realVisible && currentPage !== 1) { - fetchFriends(currentPage, searchQuery); - } - }, [currentPage, realVisible, searchQuery]); - - // 搜索防抖 - useEffect(() => { - if (!realVisible) return; - - const timer = setTimeout(() => { - setCurrentPage(1); - fetchFriends(1, searchQuery); - }, 500); - - return () => clearTimeout(timer); - }, [searchQuery, realVisible]); - - // 获取好友列表API - 添加 keyword 参数 - const fetchFriends = async (page: number, keyword: string = "") => { - setLoading(true); - try { - const params: any = { - page, - limit: 20, - }; - - if (keyword.trim()) { - params.keyword = keyword.trim(); - } - - if (enableDeviceFilter && deviceIds.length > 0) { - params.deviceIds = deviceIds.join(","); - } - - const response = await getFriendList(params); - if (response && response.list) { - setFriends(response.list); - setTotalFriends(response.total || 0); - setTotalPages(Math.ceil((response.total || 0) / 20)); - } - } catch (error) { - console.error("获取好友列表失败:", error); - } finally { - setLoading(false); - } - }; - - // 处理好友选择 - const handleFriendToggle = (friend: FriendSelectionItem) => { - if (readonly) return; - - const newSelectedFriends = selectedOptions - .map(v => v.id) - .includes(friend.id) - ? selectedOptions.filter(v => v.id !== friend.id) - : selectedOptions.concat(friend); - - onSelect(newSelectedFriends); }; // 获取显示文本 @@ -122,14 +49,13 @@ export default function FriendSelection({ onSelect(selectedOptions.filter(v => v.id !== id)); }; - // 确认选择 - const handleConfirm = () => { - if (onConfirm) { - onConfirm( - selectedOptions.map(v => v.id), - selectedOptions, - ); - } + // 弹窗确认回调 + const handleConfirm = ( + selectedIds: number[], + selectedItems: typeof selectedOptions, + ) => { + onSelect(selectedItems); + if (onConfirm) onConfirm(selectedIds, selectedItems); setRealVisible(false); }; @@ -167,151 +93,48 @@ export default function FriendSelection({ }} > {selectedOptions.map(friend => ( -
-
- {friend.nickname || friend.wechatId || friend.id} +
+
+ +
+
{friend.nickname}
+
{friend.wechatId}
+
+ {!readonly && ( +
- {!readonly && ( -
))}
)} {/* 弹窗 */} - setRealVisible(false)} - position="bottom" - bodyStyle={{ height: "100vh" }} - > - fetchFriends(currentPage, searchQuery)} - /> - } - footer={ - setRealVisible(false)} - onConfirm={handleConfirm} - /> - } - > -
- {loading ? ( -
-
加载中...
-
- ) : friends.length > 0 ? ( -
- {friends.map(friend => ( - - ))} -
- ) : ( -
-
- {deviceIds.length === 0 - ? "请先选择设备" - : searchQuery - ? `没有找到包含"${searchQuery}"的好友` - : "没有找到好友"} -
-
- )} -
-
-
+ onVisibleChange={setRealVisible} + selectedOptions={selectedOptions} + onSelect={onSelect} + deviceIds={deviceIds} + enableDeviceFilter={enableDeviceFilter} + readonly={readonly} + onConfirm={handleConfirm} + /> ); } diff --git a/nkebao/src/components/FriendSelection/selectionPopup.tsx b/nkebao/src/components/FriendSelection/selectionPopup.tsx new file mode 100644 index 00000000..f5713c80 --- /dev/null +++ b/nkebao/src/components/FriendSelection/selectionPopup.tsx @@ -0,0 +1,213 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { Popup, Checkbox } from "antd-mobile"; +import Layout from "@/components/Layout/Layout"; +import PopupHeader from "@/components/PopuLayout/header"; +import PopupFooter from "@/components/PopuLayout/footer"; +import { getFriendList } from "./api"; +import style from "./index.module.scss"; +import type { FriendSelectionItem } from "./data"; + +interface SelectionPopupProps { + visible: boolean; + onVisibleChange: (visible: boolean) => void; + selectedOptions: FriendSelectionItem[]; + onSelect: (friends: FriendSelectionItem[]) => void; + deviceIds?: string[]; + enableDeviceFilter?: boolean; + readonly?: boolean; + onConfirm?: ( + selectedIds: number[], + selectedItems: FriendSelectionItem[], + ) => void; +} + +const SelectionPopup: React.FC = ({ + visible, + onVisibleChange, + selectedOptions, + onSelect, + deviceIds = [], + enableDeviceFilter = true, + readonly = false, + onConfirm, +}) => { + const [friends, setFriends] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalFriends, setTotalFriends] = useState(0); + const [loading, setLoading] = useState(false); + + // 获取好友列表API + const fetchFriends = useCallback( + async (page: number, keyword: string = "") => { + setLoading(true); + try { + const params: any = { + page, + limit: 20, + }; + + if (keyword.trim()) { + params.keyword = keyword.trim(); + } + + if (enableDeviceFilter && deviceIds.length > 0) { + params.deviceIds = deviceIds.join(","); + } + + const response = await getFriendList(params); + if (response && response.list) { + setFriends(response.list); + setTotalFriends(response.total || 0); + setTotalPages(Math.ceil((response.total || 0) / 20)); + } + } catch (error) { + console.error("获取好友列表失败:", error); + } finally { + setLoading(false); + } + }, + [deviceIds, enableDeviceFilter], + ); + + // 处理好友选择 + const handleFriendToggle = (friend: FriendSelectionItem) => { + if (readonly) return; + + const newSelectedFriends = selectedOptions.some(f => f.id === friend.id) + ? selectedOptions.filter(f => f.id !== friend.id) + : selectedOptions.concat(friend); + + onSelect(newSelectedFriends); + }; + + // 确认选择 + const handleConfirm = () => { + if (onConfirm) { + onConfirm( + selectedOptions.map(v => v.id), + selectedOptions, + ); + } + onVisibleChange(false); + }; + + // 弹窗打开时初始化 + useEffect(() => { + if (visible) { + setCurrentPage(1); + setSearchQuery(""); + fetchFriends(1, ""); + } + }, [visible]); // 只在弹窗开启时请求 + + // 搜索防抖(只在弹窗打开且搜索词变化时执行) + useEffect(() => { + if (!visible || searchQuery === "") return; // 弹窗关闭或搜索词为空时不请求 + + const timer = setTimeout(() => { + setCurrentPage(1); + fetchFriends(1, searchQuery); + }, 500); + + return () => clearTimeout(timer); + }, [searchQuery, visible]); + + // 页码变化时请求数据(只在弹窗打开且页码不是1时执行) + useEffect(() => { + if (!visible || currentPage === 1) return; // 弹窗关闭或第一页时不请求 + fetchFriends(currentPage, searchQuery); + }, [currentPage, visible, searchQuery]); + + return ( + onVisibleChange(false)} + position="bottom" + bodyStyle={{ height: "100vh" }} + > + fetchFriends(currentPage, searchQuery)} + /> + } + footer={ + onVisibleChange(false)} + onConfirm={handleConfirm} + /> + } + > +
+ {loading ? ( +
+
加载中...
+
+ ) : friends.length > 0 ? ( +
+ {friends.map(friend => ( +
+ f.id === friend.id)} + onChange={() => !readonly && handleFriendToggle(friend)} + disabled={readonly} + style={{ marginRight: 12 }} + /> +
+
+ {friend.avatar ? ( + {friend.nickname} + ) : ( + friend.nickname.charAt(0) + )} +
+
+
{friend.nickname}
+
+ 微信ID: {friend.wechatId} +
+ {friend.customer && ( +
+ 归属客户: {friend.customer} +
+ )} +
+
+
+ ))} +
+ ) : ( +
+
+ {deviceIds.length === 0 + ? "请先选择设备" + : searchQuery + ? `没有找到包含"${searchQuery}"的好友` + : "没有找到好友"} +
+
+ )} +
+
+
+ ); +}; + +export default SelectionPopup; diff --git a/nkebao/src/pages/mobile/test/select.tsx b/nkebao/src/pages/mobile/test/select.tsx index 65dc43e7..9dea5a6a 100644 --- a/nkebao/src/pages/mobile/test/select.tsx +++ b/nkebao/src/pages/mobile/test/select.tsx @@ -12,14 +12,11 @@ import { GroupSelectionItem } from "@/components/GroupSelection/data"; import { ContentItem } from "@/components/ContentSelection/data"; import { FriendSelectionItem } from "@/components/FriendSelection/data"; const ComponentTest: React.FC = () => { - const [activeTab, setActiveTab] = useState("libraries"); + const [activeTab, setActiveTab] = useState("friends"); // 设备选择状态 const [selectedDevices, setSelectedDevices] = useState([]); - // 好友选择状态 - const [selectedFriends, setSelectedFriends] = useState([]); - // 群组选择状态 const [selectedGroups, setSelectedGroups] = useState( [], @@ -29,6 +26,7 @@ const ComponentTest: React.FC = () => { const [selectedContent, setSelectedContent] = useState([]); const [selectedAccounts, setSelectedAccounts] = useState([]); + // 好友选择状态 const [selectedFriendsOptions, setSelectedFriendsOptions] = useState< FriendSelectionItem[] >([]); From b7d94b56f003a8665ace3bfc834336209b4523a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 8 Aug 2025 14:58:26 +0800 Subject: [PATCH 2/2] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E9=80=89=E6=8B=A9=E6=9E=84=E5=BB=BA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/components/DeviceSelection/data.ts | 6 ++-- .../src/components/DeviceSelection/index.tsx | 12 ++++---- .../DeviceSelection/selectionPopup.tsx | 29 +++++++------------ nkebao/src/pages/mobile/test/select.tsx | 10 +++++-- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/nkebao/src/components/DeviceSelection/data.ts b/nkebao/src/components/DeviceSelection/data.ts index e8a09b5f..f493e071 100644 --- a/nkebao/src/components/DeviceSelection/data.ts +++ b/nkebao/src/components/DeviceSelection/data.ts @@ -1,6 +1,6 @@ // 设备选择项接口 export interface DeviceSelectionItem { - id: string; + id: number; name: string; imei: string; wechatId: string; @@ -12,8 +12,8 @@ export interface DeviceSelectionItem { // 组件属性接口 export interface DeviceSelectionProps { - selectedOptions: string[]; - onSelect: (devices: string[]) => void; + selectedOptions: DeviceSelectionItem[]; + onSelect: (devices: DeviceSelectionItem[]) => void; placeholder?: string; className?: string; mode?: "input" | "dialog"; // 新增,默认input diff --git a/nkebao/src/components/DeviceSelection/index.tsx b/nkebao/src/components/DeviceSelection/index.tsx index c358e0ff..d3f9bfc5 100644 --- a/nkebao/src/components/DeviceSelection/index.tsx +++ b/nkebao/src/components/DeviceSelection/index.tsx @@ -41,9 +41,9 @@ const DeviceSelection: React.FC = ({ }; // 删除已选设备 - const handleRemoveDevice = (id: string) => { + const handleRemoveDevice = (id: number) => { if (readonly) return; - onSelect(selectedOptions.filter(d => d !== id)); + onSelect(selectedOptions.filter(v => v.id !== id)); }; return ( @@ -79,9 +79,9 @@ const DeviceSelection: React.FC = ({ background: "#fff", }} > - {selectedOptions.map(deviceId => ( + {selectedOptions.map(device => (
= ({ textOverflow: "ellipsis", }} > - {deviceId} + 【 {device.name}】 - {device.wechatId}
{!readonly && (
diff --git a/nkebao/src/components/DeviceSelection/selectionPopup.tsx b/nkebao/src/components/DeviceSelection/selectionPopup.tsx index 49a933e1..daaca8e4 100644 --- a/nkebao/src/components/DeviceSelection/selectionPopup.tsx +++ b/nkebao/src/components/DeviceSelection/selectionPopup.tsx @@ -5,23 +5,13 @@ import style from "./index.module.scss"; import Layout from "@/components/Layout/Layout"; import PopupHeader from "@/components/PopuLayout/header"; import PopupFooter from "@/components/PopuLayout/footer"; - -interface DeviceSelectionItem { - id: string; - name: string; - imei: string; - wechatId: string; - status: "online" | "offline"; - wxid?: string; - nickname?: string; - usedInPlans?: number; -} +import { DeviceSelectionItem } from "./data"; interface SelectionPopupProps { visible: boolean; onClose: () => void; - selectedOptions: string[]; - onSelect: (devices: string[]) => void; + selectedOptions: DeviceSelectionItem[]; + onSelect: (devices: DeviceSelectionItem[]) => void; } const PAGE_SIZE = 20; @@ -112,11 +102,12 @@ const SelectionPopup: React.FC = ({ const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); // 处理设备选择 - const handleDeviceToggle = (deviceId: string) => { - if (selectedOptions.includes(deviceId)) { - onSelect(selectedOptions.filter(id => id !== deviceId)); + const handleDeviceToggle = (device: DeviceSelectionItem) => { + if (selectedOptions.some(v => v.id === device.id)) { + onSelect(selectedOptions.filter(v => v.id !== device.id)); } else { - onSelect([...selectedOptions, deviceId]); + const newSelectedOptions = [...selectedOptions, device]; + onSelect(newSelectedOptions); } }; @@ -172,8 +163,8 @@ const SelectionPopup: React.FC = ({ {filteredDevices.map(device => (