FEAT => 本次更新项目为:
好友选择构建完成
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<FriendSelectionItem[]>([]);
|
||||
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 => (
|
||||
<div
|
||||
key={friend.id}
|
||||
className={style.selectedListRow}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "4px 8px",
|
||||
borderBottom: "1px solid #f0f0f0",
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{friend.nickname || friend.wechatId || friend.id}
|
||||
<div key={friend.id} className={style.selectedListRow}>
|
||||
<div className={style.selectedListRowContent}>
|
||||
<Avatar src={friend.avatar} />
|
||||
<div className={style.selectedListRowContentText}>
|
||||
<div>{friend.nickname}</div>
|
||||
<div>{friend.wechatId}</div>
|
||||
</div>
|
||||
{!readonly && (
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 4,
|
||||
color: "#ff4d4f",
|
||||
border: "none",
|
||||
background: "none",
|
||||
minWidth: 24,
|
||||
height: 24,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={() => handleRemoveFriend(friend.id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!readonly && (
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 4,
|
||||
color: "#ff4d4f",
|
||||
border: "none",
|
||||
background: "none",
|
||||
minWidth: 24,
|
||||
height: 24,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onClick={() => handleRemoveFriend(friend.id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* 弹窗 */}
|
||||
<Popup
|
||||
<SelectionPopup
|
||||
visible={realVisible && !readonly}
|
||||
onMaskClick={() => setRealVisible(false)}
|
||||
position="bottom"
|
||||
bodyStyle={{ height: "100vh" }}
|
||||
>
|
||||
<Layout
|
||||
header={
|
||||
<PopupHeader
|
||||
title="选择微信好友"
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
searchPlaceholder="搜索好友"
|
||||
loading={loading}
|
||||
onRefresh={() => fetchFriends(currentPage, searchQuery)}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<PopupFooter
|
||||
total={totalFriends}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
loading={loading}
|
||||
selectedCount={selectedOptions.length}
|
||||
onPageChange={setCurrentPage}
|
||||
onCancel={() => setRealVisible(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={style.friendList}>
|
||||
{loading ? (
|
||||
<div className={style.loadingBox}>
|
||||
<div className={style.loadingText}>加载中...</div>
|
||||
</div>
|
||||
) : friends.length > 0 ? (
|
||||
<div className={style.friendListInner}>
|
||||
{friends.map(friend => (
|
||||
<label
|
||||
key={friend.id}
|
||||
className={style.friendItem}
|
||||
onClick={() => !readonly && handleFriendToggle(friend)}
|
||||
>
|
||||
<div className={style.radioWrapper}>
|
||||
<div
|
||||
className={
|
||||
selectedOptions.map(v => v.id).includes(friend.id)
|
||||
? style.radioSelected
|
||||
: style.radioUnselected
|
||||
}
|
||||
>
|
||||
{selectedOptions.map(v => v.id).includes(friend.id) && (
|
||||
<div className={style.radioDot}></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.friendInfo}>
|
||||
<div className={style.friendAvatar}>
|
||||
{friend.avatar ? (
|
||||
<img
|
||||
src={friend.avatar}
|
||||
alt={friend.nickname}
|
||||
className={style.avatarImg}
|
||||
/>
|
||||
) : (
|
||||
friend.nickname.charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div className={style.friendDetail}>
|
||||
<div className={style.friendName}>
|
||||
{friend.nickname}
|
||||
</div>
|
||||
<div className={style.friendId}>
|
||||
微信ID: {friend.wechatId}
|
||||
</div>
|
||||
{friend.customer && (
|
||||
<div className={style.friendCustomer}>
|
||||
归属客户: {friend.customer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.emptyBox}>
|
||||
<div className={style.emptyText}>
|
||||
{deviceIds.length === 0
|
||||
? "请先选择设备"
|
||||
: searchQuery
|
||||
? `没有找到包含"${searchQuery}"的好友`
|
||||
: "没有找到好友"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
</Popup>
|
||||
onVisibleChange={setRealVisible}
|
||||
selectedOptions={selectedOptions}
|
||||
onSelect={onSelect}
|
||||
deviceIds={deviceIds}
|
||||
enableDeviceFilter={enableDeviceFilter}
|
||||
readonly={readonly}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
213
nkebao/src/components/FriendSelection/selectionPopup.tsx
Normal file
213
nkebao/src/components/FriendSelection/selectionPopup.tsx
Normal file
@@ -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<SelectionPopupProps> = ({
|
||||
visible,
|
||||
onVisibleChange,
|
||||
selectedOptions,
|
||||
onSelect,
|
||||
deviceIds = [],
|
||||
enableDeviceFilter = true,
|
||||
readonly = false,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const [friends, setFriends] = useState<FriendSelectionItem[]>([]);
|
||||
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 (
|
||||
<Popup
|
||||
visible={visible && !readonly}
|
||||
onMaskClick={() => onVisibleChange(false)}
|
||||
position="bottom"
|
||||
bodyStyle={{ height: "100vh" }}
|
||||
>
|
||||
<Layout
|
||||
header={
|
||||
<PopupHeader
|
||||
title="选择微信好友"
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
searchPlaceholder="搜索好友"
|
||||
loading={loading}
|
||||
onRefresh={() => fetchFriends(currentPage, searchQuery)}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<PopupFooter
|
||||
total={totalFriends}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
loading={loading}
|
||||
selectedCount={selectedOptions.length}
|
||||
onPageChange={setCurrentPage}
|
||||
onCancel={() => onVisibleChange(false)}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={style.friendList}>
|
||||
{loading ? (
|
||||
<div className={style.loadingBox}>
|
||||
<div className={style.loadingText}>加载中...</div>
|
||||
</div>
|
||||
) : friends.length > 0 ? (
|
||||
<div className={style.friendListInner}>
|
||||
{friends.map(friend => (
|
||||
<div key={friend.id} className={style.friendItem}>
|
||||
<Checkbox
|
||||
checked={selectedOptions.some(f => f.id === friend.id)}
|
||||
onChange={() => !readonly && handleFriendToggle(friend)}
|
||||
disabled={readonly}
|
||||
style={{ marginRight: 12 }}
|
||||
/>
|
||||
<div className={style.friendInfo}>
|
||||
<div className={style.friendAvatar}>
|
||||
{friend.avatar ? (
|
||||
<img
|
||||
src={friend.avatar}
|
||||
alt={friend.nickname}
|
||||
className={style.avatarImg}
|
||||
/>
|
||||
) : (
|
||||
friend.nickname.charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div className={style.friendDetail}>
|
||||
<div className={style.friendName}>{friend.nickname}</div>
|
||||
<div className={style.friendId}>
|
||||
微信ID: {friend.wechatId}
|
||||
</div>
|
||||
{friend.customer && (
|
||||
<div className={style.friendCustomer}>
|
||||
归属客户: {friend.customer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={style.emptyBox}>
|
||||
<div className={style.emptyText}>
|
||||
{deviceIds.length === 0
|
||||
? "请先选择设备"
|
||||
: searchQuery
|
||||
? `没有找到包含"${searchQuery}"的好友`
|
||||
: "没有找到好友"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectionPopup;
|
||||
@@ -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<string[]>([]);
|
||||
|
||||
// 好友选择状态
|
||||
const [selectedFriends, setSelectedFriends] = useState<string[]>([]);
|
||||
|
||||
// 群组选择状态
|
||||
const [selectedGroups, setSelectedGroups] = useState<GroupSelectionItem[]>(
|
||||
[],
|
||||
@@ -29,6 +26,7 @@ const ComponentTest: React.FC = () => {
|
||||
const [selectedContent, setSelectedContent] = useState<ContentItem[]>([]);
|
||||
|
||||
const [selectedAccounts, setSelectedAccounts] = useState<number[]>([]);
|
||||
// 好友选择状态
|
||||
const [selectedFriendsOptions, setSelectedFriendsOptions] = useState<
|
||||
FriendSelectionItem[]
|
||||
>([]);
|
||||
|
||||
Reference in New Issue
Block a user