feat: 添加成员功能完成

This commit is contained in:
超级老白兔
2025-11-18 11:57:54 +08:00
parent 1582e22756
commit 7d9f9fbd08
2 changed files with 173 additions and 59 deletions

View File

@@ -17,6 +17,7 @@ const FriendListItem = memo<{
onClick={() => onSelect(friend)}
>
<Checkbox checked={isSelected} />
&nbsp;&nbsp;&nbsp;
<Avatar src={friend.avatar} size={40}>
{friend.nickname?.charAt(0)}
</Avatar>
@@ -41,6 +42,9 @@ interface TwoColumnSelectionProps {
deviceIds?: number[];
enableDeviceFilter?: boolean;
dataSource?: FriendSelectionItem[];
onLoadMore?: () => void; // 加载更多回调
hasMore?: boolean; // 是否有更多数据
loading?: boolean; // 是否正在加载
}
const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
@@ -51,13 +55,16 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
deviceIds = [],
enableDeviceFilter = true,
dataSource,
onLoadMore,
hasMore = false,
loading = false,
}) => {
const [rawFriends, setRawFriends] = useState<FriendSelectionItem[]>([]);
const [selectedFriends, setSelectedFriends] = useState<FriendSelectionItem[]>(
[],
);
const [searchQuery, setSearchQuery] = useState("");
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
@@ -81,10 +88,10 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
const [displayPage, setDisplayPage] = useState(1);
const friends = useMemo(() => {
const startIndex = 0;
const endIndex = displayPage * ITEMS_PER_PAGE;
return filteredFriends.slice(startIndex, endIndex);
}, [filteredFriends, displayPage]);
// 直接使用完整的过滤列表,不再进行本地分页
// 因为我们已经在外部进行了分页加载
return filteredFriends;
}, [filteredFriends]);
const hasMoreFriends = filteredFriends.length > friends.length;
@@ -100,7 +107,7 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
// 获取好友列表
const fetchFriends = useCallback(
async (page: number, keyword: string = "") => {
setLoading(true);
setIsLoading(true);
try {
const params: any = {
page,
@@ -128,7 +135,7 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
console.error("获取好友列表失败:", error);
message.error("获取好友列表失败");
} finally {
setLoading(false);
setIsLoading(false);
}
},
[deviceIds, enableDeviceFilter],
@@ -148,7 +155,7 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
if (visible) {
setSearchQuery("");
setSelectedFriends([]);
setLoading(false);
setIsLoading(false);
}
}, [visible]);
@@ -257,7 +264,7 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
</div>
<div className={styles.friendList}>
{loading ? (
{isLoading && !loading ? (
<div className={styles.loading}>...</div>
) : friends.length > 0 ? (
// 使用 React.memo 优化列表项渲染
@@ -280,9 +287,14 @@ const TwoColumnSelection: React.FC<TwoColumnSelectionProps> = ({
</div>
)}
{hasMoreFriends && (
{/* 使用外部传入的加载更多 */}
{hasMore && (
<div className={styles.loadMoreWrapper}>
<Button type="link" onClick={handleLoadMore} loading={loading}>
<Button
type="link"
onClick={() => (onLoadMore ? onLoadMore() : handleLoadMore())}
loading={loading}
>
</Button>
</div>

View File

@@ -25,7 +25,7 @@ import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { useCustomerStore } from "@/store/module/weChat/customer";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { useContactStore } from "@/store/module/weChat/contacts";
import { contactUnifiedService } from "@/utils/db";
import { generateAiText } from "@/api/ai";
import TwoColumnSelection from "@/components/TwoColumnSelection/TwoColumnSelection";
import TwoColumnMemberSelection from "@/components/MemberSelection/TwoColumnMemberSelection";
@@ -216,7 +216,7 @@ const Person: React.FC<PersonProps> = ({ contract }) => {
return matchedCustomer || null;
}, [customerList, contract.wechatAccountId]);
const { getContactsByCustomer } = useContactStore();
// 不再需要从useContactStore获取getContactsByCustomer
const { sendCommand } = useWebSocketStore();
@@ -516,6 +516,125 @@ const Person: React.FC<PersonProps> = ({ contract }) => {
bio: contract.bio || contract.signature || "-",
};
// 分页状态
const [currentContactPage, setCurrentContactPage] = useState(1);
const [contactPageSize] = useState(10);
const [isLoadingContacts, setIsLoadingContacts] = useState(false);
// 从数据库获取联系人数据的通用函数
const fetchContacts = async (page = 1) => {
try {
const { databaseManager, initializeDatabaseFromPersistedUser } =
await import("@/utils/db");
// 检查数据库初始化状态
if (!databaseManager.isInitialized()) {
await initializeDatabaseFromPersistedUser();
}
// 获取当前用户ID
const userId = kfSelectedUser?.userId || 0;
const storeUserId = databaseManager.getCurrentUserId();
const effectiveUserId = storeUserId || userId;
if (!effectiveUserId) {
messageApi.error("无法获取用户信息,请尝试重新登录");
return [];
}
// 查询联系人数据
const allContacts = await contactUnifiedService.findWhereMultiple([
{ field: "userId", operator: "equals", value: effectiveUserId },
{
field: "wechatAccountId",
operator: "equals",
value: contract.wechatAccountId,
},
{ field: "type", operator: "equals", value: "friend" },
]);
// 手动分页
const startIndex = (page - 1) * contactPageSize;
const endIndex = startIndex + contactPageSize;
return allContacts.slice(startIndex, endIndex);
} catch (error) {
console.error("获取联系人数据失败:", error);
messageApi.error("获取联系人数据失败");
return [];
}
};
const addMember = async () => {
try {
setIsLoadingContacts(true);
const pagedContacts = await fetchContacts(currentContactPage);
// 转换为选择器需要的数据格式
const friendSelectionData = pagedContacts.map(item => ({
id: item.id || item.serverId,
wechatId: item.wechatId,
nickname: item.nickname,
avatar: item.avatar || "",
conRemark: item.conRemark,
name: item.conRemark || item.nickname, // 用于搜索显示
}));
setContractList(friendSelectionData);
setIsFriendSelectionVisible(true);
// 如果没有联系人数据,显示提示
if (friendSelectionData.length === 0) {
messageApi.info("未找到可添加的联系人,可能需要先同步联系人数据");
}
} catch (error) {
console.error("获取联系人列表失败:", error);
messageApi.error("获取联系人列表失败");
} finally {
setIsLoadingContacts(false);
}
};
// 加载更多联系人
const loadMoreContacts = async () => {
if (isLoadingContacts) return;
try {
setIsLoadingContacts(true);
const nextPage = currentContactPage + 1;
setCurrentContactPage(nextPage);
// 使用通用函数获取下一页联系人数据
const pagedContacts = await fetchContacts(nextPage);
// 转换数据格式
const newFriendSelectionData = pagedContacts.map(item => ({
id: item.id || item.serverId,
wechatId: item.wechatId,
nickname: item.nickname,
avatar: item.avatar || "",
conRemark: item.conRemark,
name: item.conRemark || item.nickname,
}));
// 更新列表并去重
setContractList(prev => {
const newList = [...prev, ...newFriendSelectionData];
// 确保列表中没有重复项
const uniqueMap = new Map();
const uniqueList = newList.filter(item => {
if (uniqueMap.has(item.id)) {
return false;
}
uniqueMap.set(item.id, true);
return true;
});
return uniqueList;
});
messageApi.success(`已加载${pagedContacts.length}条联系人数据`);
} catch (error) {
console.error("加载更多联系人失败:", error);
messageApi.error("加载更多联系人失败");
} finally {
setIsLoadingContacts(false);
}
};
return (
<>
{contextHolder}
@@ -774,27 +893,25 @@ const Person: React.FC<PersonProps> = ({ contract }) => {
<Card title="标签" className={styles.profileCard}>
<div className={styles.tagsContainer}>
{/* 渲染所有可用标签,选中的排在前面 */}
{[...new Set([...selectedTags, ...allAvailableTags])].map(
(tag, index) => {
const isSelected = selectedTags.includes(tag);
return (
<Tag
key={tag}
color={isSelected ? "blue" : "default"}
style={{
cursor: "pointer",
border: isSelected
? "1px solid #1890ff"
: "1px solid #d9d9d9",
backgroundColor: isSelected ? "#e6f7ff" : "#fafafa",
}}
onClick={() => handleTagToggle(tag)}
>
{tag}
</Tag>
);
},
)}
{[...new Set([...selectedTags, ...allAvailableTags])].map(tag => {
const isSelected = selectedTags.includes(tag);
return (
<Tag
key={tag}
color={isSelected ? "blue" : "default"}
style={{
cursor: "pointer",
border: isSelected
? "1px solid #1890ff"
: "1px solid #d9d9d9",
backgroundColor: isSelected ? "#e6f7ff" : "#fafafa",
}}
onClick={() => handleTagToggle(tag)}
>
{tag}
</Tag>
);
})}
{/* 新增标签区域 */}
{isAddingTag ? (
@@ -917,29 +1034,7 @@ const Person: React.FC<PersonProps> = ({ contract }) => {
>
<Button
icon={<PlusOutlined />}
onClick={async () => {
try {
const contractData = getContactsByCustomer(
contract.wechatAccountId,
);
// 转换 Contact[] 为 FriendSelectionItem[]
const friendSelectionData = (contractData || []).map(
item => ({
id: item.id || item.serverId,
wechatId: item.wechatId,
nickname: item.nickname,
avatar: item.avatar || "",
conRemark: item.conRemark,
name: item.conRemark || item.nickname, // 用于搜索显示
}),
);
setContractList(friendSelectionData);
setIsFriendSelectionVisible(true);
} catch (error) {
console.error("获取联系人列表失败:", error);
messageApi.error("获取联系人列表失败");
}
}}
onClick={addMember}
type="primary"
style={{
flex: 1,
@@ -1028,7 +1123,7 @@ const Person: React.FC<PersonProps> = ({ contract }) => {
className={styles.groupMemberList}
style={{ maxHeight: "400px", overflowY: "auto" }}
>
{currentGroupMembers.map((member, index) => (
{currentGroupMembers.map(member => (
<Tooltip title={member.nickname} key={member.wechatId}>
<div
className={styles.groupMember}
@@ -1259,15 +1354,22 @@ const Person: React.FC<PersonProps> = ({ contract }) => {
{/* 添加成员弹窗 */}
<TwoColumnSelection
visible={isFriendSelectionVisible}
onCancel={() => setIsFriendSelectionVisible(false)}
onCancel={() => {
setIsFriendSelectionVisible(false);
setCurrentContactPage(1); // 重置页码
}}
onConfirm={(selectedIds, selectedItems) => {
handleAddMember(
selectedIds.map(id => parseInt(id)),
selectedItems,
);
setCurrentContactPage(1); // 重置页码
}}
dataSource={contractList}
title="添加群成员"
onLoadMore={loadMoreContacts}
hasMore={true} // 强制设置为true确保显示加载更多按钮
loading={isLoadingContacts}
/>
{/* 删除成员弹窗 */}