feat: 添加成员功能完成
This commit is contained in:
@@ -17,6 +17,7 @@ const FriendListItem = memo<{
|
||||
onClick={() => onSelect(friend)}
|
||||
>
|
||||
<Checkbox checked={isSelected} />
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
{/* 删除成员弹窗 */}
|
||||
|
||||
Reference in New Issue
Block a user