【操盘手】 内容库 - 操作优化

This commit is contained in:
wong
2025-04-23 18:10:43 +08:00
parent b324c87488
commit f036971de4
6 changed files with 128 additions and 103 deletions

View File

@@ -31,6 +31,7 @@ interface ContentLibraryDetail {
selectedFriends?: WechatFriend[] selectedFriends?: WechatFriend[]
selectedGroups?: WechatGroup[] selectedGroups?: WechatGroup[]
selectedGroupMembers?: WechatGroupMember[] selectedGroupMembers?: WechatGroupMember[]
groupMembers?: Record<string, number[]>
keywordInclude: string[] keywordInclude: string[]
keywordExclude: string[] keywordExclude: string[]
isEnabled: number isEnabled: number
@@ -88,7 +89,37 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i
// 直接使用API返回的好友和群组数据 // 直接使用API返回的好友和群组数据
const friends = data.selectedFriends || []; const friends = data.selectedFriends || [];
const groups = data.selectedGroups || []; const groups = data.selectedGroups || [];
const groupMembers = data.selectedGroupMembers || [];
// 根据接口返回的groupMembers字段构建selectedGroupMembers数组
let groupMembers: WechatGroupMember[] = [];
// 检查是否有groupMembers数据
if (data.groupMembers) {
// 将 {"813941": [262439]} 格式转换为 WechatGroupMember[] 数组
Object.entries(data.groupMembers).forEach(([groupId, memberIds]) => {
// 确保memberIds是数组
if (Array.isArray(memberIds)) {
memberIds.forEach(memberId => {
// 在groups中查找对应的group信息
const group = groups.find(g => g.id === groupId);
if (group) {
// 将group信息与member ID组合成 WechatGroupMember 对象
groupMembers.push({
id: String(memberId),
groupId: groupId,
nickname: "", // 默认为空实际使用时会由WechatGroupMemberSelector填充
avatar: "", // 默认为空实际使用时会由WechatGroupMemberSelector填充
wechatId: "" // 添加必要的wechatId字段
});
}
});
}
});
} else if (data.selectedGroupMembers) {
// 如果有selectedGroupMembers字段则直接使用
groupMembers = data.selectedGroupMembers;
}
setFormData({ setFormData({
name: data.name, name: data.name,
@@ -160,13 +191,25 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i
const loadingToast = showToast("正在更新内容库...", "loading", true); const loadingToast = showToast("正在更新内容库...", "loading", true);
try { try {
// 将群成员数据转换为{"813941": [262439]}格式
const groupMembersMap: Record<string, string[]> = {}
formData.selectedGroupMembers.forEach(member => {
if (member.groupId) {
if (!groupMembersMap[member.groupId]) {
groupMembersMap[member.groupId] = []
}
groupMembersMap[member.groupId].push(member.id)
}
})
const payload = { const payload = {
id: resolvedParams.id, id: resolvedParams.id,
name: formData.name, name: formData.name,
sourceType: formData.sourceType === "friends" ? 1 : 2, sourceType: formData.sourceType === "friends" ? 1 : 2,
friends: formData.selectedFriends.map(f => f.id), friends: formData.selectedFriends.map(f => f.id),
groups: formData.selectedGroups.map(g => g.id), groups: formData.selectedGroups.map(g => g.id),
groupMembers: formData.selectedGroupMembers.map(m => m.id), groupMembers: groupMembersMap, // 使用JSON字符串传递群成员数据
keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean), keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean),
keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean), keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean),
aiPrompt: formData.useAI ? formData.aiPrompt : "", aiPrompt: formData.useAI ? formData.aiPrompt : "",
@@ -283,16 +326,21 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i
alt={group.name} alt={group.name}
className="w-8 h-8 rounded-full" className="w-8 h-8 rounded-full"
/> />
<span>{group.name}</span> <span className="truncate max-w-[220px]">{group.name}</span>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => handleSelectGroupMembers(group.id)} onClick={() => handleSelectGroupMembers(group.id)}
className="mr-1" className={`mr-1 ${formData.selectedGroupMembers.some(m => m.groupId === group.id) ? 'text-blue-500' : ''}`}
> >
<Users className="h-4 w-4" /> <Users className="h-4 w-4" />
{formData.selectedGroupMembers.some(m => m.groupId === group.id) && (
<span className="ml-1 text-xs">
+{formData.selectedGroupMembers.filter(m => m.groupId === group.id).length}
</span>
)}
</Button> </Button>
<Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}> <Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}>
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@@ -302,43 +350,6 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i
))} ))}
</div> </div>
)} )}
{formData.selectedGroupMembers.length > 0 && (
<div className="mt-4">
<div className="flex items-center justify-between mb-2">
<Label className="text-sm font-medium"> ({formData.selectedGroupMembers.length})</Label>
<Button
variant="ghost"
size="sm"
onClick={() => setFormData({...formData, selectedGroupMembers: []})}
>
</Button>
</div>
<div className="flex flex-wrap gap-2 p-2 bg-gray-50 rounded-md max-h-[150px] overflow-y-auto">
{formData.selectedGroupMembers.map((member) => (
<div key={member.id} className="flex items-center bg-white px-2 py-1 rounded border">
<Avatar className="h-6 w-6 mr-2">
<AvatarImage src={member.avatar} />
<AvatarFallback>{member.nickname?.[0] || '?'}</AvatarFallback>
</Avatar>
<span className="text-sm">{member.nickname}</span>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 ml-1"
onClick={() => setFormData({
...formData,
selectedGroupMembers: formData.selectedGroupMembers.filter(m => m.id !== member.id)
})}
>
<X className="h-3 w-3" />
</Button>
</div>
))}
</div>
</div>
)}
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>

View File

@@ -88,12 +88,24 @@ export default function NewContentLibraryPage() {
setLoading(true) setLoading(true)
try { try {
// 将群成员数据转换为{"群ID":[成员ID1, 成员ID2...]}的格式
const groupMembersMap: Record<string, string[]> = {}
formData.selectedGroupMembers.forEach(member => {
if (member.groupId) {
if (!groupMembersMap[member.groupId]) {
groupMembersMap[member.groupId] = []
}
groupMembersMap[member.groupId].push(member.id)
}
})
const payload = { const payload = {
name: formData.name, name: formData.name,
sourceType: formData.sourceType === "friends" ? 1 : 2, sourceType: formData.sourceType === "friends" ? 1 : 2,
friends: formData.selectedFriends.map(f => f.id), friends: formData.selectedFriends.map(f => f.id),
groups: formData.selectedGroups.map(g => g.id), groups: formData.selectedGroups.map(g => g.id),
groupMembers: formData.selectedGroupMembers.map(m => m.id), groupMembers: groupMembersMap, // 使用JSON字符串传递群成员数据
keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean), keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean),
keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean), keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean),
aiPrompt: formData.useAI ? formData.aiPrompt : "", aiPrompt: formData.useAI ? formData.aiPrompt : "",
@@ -196,16 +208,21 @@ export default function NewContentLibraryPage() {
alt={group.name} alt={group.name}
className="w-8 h-8 rounded-full" className="w-8 h-8 rounded-full"
/> />
<span>{group.name}</span> <span className="truncate max-w-[220px]">{group.name}</span>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => handleSelectGroupMembers(group.id)} onClick={() => handleSelectGroupMembers(group.id)}
className="mr-1" className={`mr-1 ${formData.selectedGroupMembers.some(m => m.groupId === group.id) ? 'text-blue-500' : ''}`}
> >
<Users className="h-4 w-4" /> <Users className="h-4 w-4" />
{formData.selectedGroupMembers.some(m => m.groupId === group.id) && (
<span className="ml-1 text-xs">
+{formData.selectedGroupMembers.filter(m => m.groupId === group.id).length}
</span>
)}
</Button> </Button>
<Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}> <Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}>
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@@ -215,43 +232,6 @@ export default function NewContentLibraryPage() {
))} ))}
</div> </div>
)} )}
{formData.selectedGroupMembers.length > 0 && (
<div className="mt-4">
<div className="flex items-center justify-between mb-2">
<Label className="text-sm font-medium"> ({formData.selectedGroupMembers.length})</Label>
<Button
variant="ghost"
size="sm"
onClick={() => setFormData({...formData, selectedGroupMembers: []})}
>
</Button>
</div>
<div className="flex flex-wrap gap-2 p-2 bg-gray-50 rounded-md max-h-[150px] overflow-y-auto">
{formData.selectedGroupMembers.map((member) => (
<div key={member.id} className="flex items-center bg-white px-2 py-1 rounded border">
<Avatar className="h-6 w-6 mr-2">
<AvatarImage src={member.avatar} />
<AvatarFallback>{member.nickname?.[0] || '?'}</AvatarFallback>
</Avatar>
<span className="text-sm">{member.nickname}</span>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 ml-1"
onClick={() => setFormData({
...formData,
selectedGroupMembers: formData.selectedGroupMembers.filter(m => m.id !== member.id)
})}
>
<X className="h-3 w-3" />
</Button>
</div>
))}
</div>
</div>
)}
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>

View File

@@ -0,0 +1 @@

View File

@@ -49,7 +49,10 @@ export function WechatGroupMemberSelector({
useEffect(() => { useEffect(() => {
if (open && groupId) { if (open && groupId) {
fetchGroupMembers(1) fetchGroupMembers(1)
setTempSelectedMembers([...selectedMembers])
// 过滤出当前群组的已选成员
const currentGroupSelectedMembers = selectedMembers.filter(member => member.groupId === groupId)
setTempSelectedMembers(currentGroupSelectedMembers)
} }
}, [open, groupId, selectedMembers]) }, [open, groupId, selectedMembers])
@@ -77,16 +80,39 @@ export function WechatGroupMemberSelector({
role = "member"; role = "member";
} }
return { const memberId = item.id || `member-${Math.random()}`;
id: item.id || `member-${Math.random()}`,
// 创建成员对象
const member = {
id: memberId,
nickname: item.name || item.ownerNickname || '未知成员', nickname: item.name || item.ownerNickname || '未知成员',
wechatId: item.ownerWechatId || item.identifier || '', wechatId: item.ownerWechatId || item.identifier || '',
avatar: item.ownerAvatar || item.avatar || '/placeholder.svg', avatar: item.ownerAvatar || item.avatar || '/placeholder.svg',
role: role, role: role,
joinTime: item.createTime ? new Date(item.createTime * 1000).toLocaleString() : '--' joinTime: item.createTime ? new Date(item.createTime * 1000).toLocaleString() : '--',
groupId: groupId
}; };
return member;
}); });
// 处理已选择的成员
// 如果已选择的成员在新获取的成员列表中,更新其信息
const updatedTempSelected = tempSelectedMembers.map(selected => {
const foundInNewList = membersList.find(m => m.id === selected.id);
if (foundInNewList) {
return {
...selected,
nickname: foundInNewList.nickname,
avatar: foundInNewList.avatar,
wechatId: foundInNewList.wechatId,
role: foundInNewList.role
};
}
return selected;
});
setTempSelectedMembers(updatedTempSelected);
setMembers(membersList) setMembers(membersList)
setTotalItems(response.data.total) setTotalItems(response.data.total)
setTotalPages(Math.ceil(response.data.total / pageSize)) setTotalPages(Math.ceil(response.data.total / pageSize))
@@ -118,6 +144,16 @@ export function WechatGroupMemberSelector({
} }
} }
const handleSelect = (member: WechatGroupMember, checked: boolean) => {
if (checked) {
// 添加成员
setTempSelectedMembers([...tempSelectedMembers, member]);
} else {
// 移除成员
setTempSelectedMembers(tempSelectedMembers.filter(m => m.id !== member.id));
}
}
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
@@ -154,13 +190,7 @@ export function WechatGroupMemberSelector({
<div key={member.id} className="flex items-center space-x-3 p-2 hover:bg-gray-100 rounded-lg"> <div key={member.id} className="flex items-center space-x-3 p-2 hover:bg-gray-100 rounded-lg">
<Checkbox <Checkbox
checked={tempSelectedMembers.some((m) => m.id === member.id)} checked={tempSelectedMembers.some((m) => m.id === member.id)}
onCheckedChange={(checked) => { onCheckedChange={(checked) => handleSelect(member, checked === true)}
if (checked) {
setTempSelectedMembers([...tempSelectedMembers, member])
} else {
setTempSelectedMembers(tempSelectedMembers.filter((m) => m.id !== member.id))
}
}}
/> />
<Avatar> <Avatar>
<AvatarImage src={member.avatar} /> <AvatarImage src={member.avatar} />
@@ -216,8 +246,10 @@ export function WechatGroupMemberSelector({
</Button> </Button>
<Button onClick={() => { <Button onClick={() => {
onSelect(tempSelectedMembers) // 从selectedMembers中移除当前群组的所有成员然后添加新选择的成员
onOpenChange(false) const otherGroupMembers = selectedMembers.filter(member => member.groupId !== groupId);
onSelect([...otherGroupMembers, ...tempSelectedMembers]);
onOpenChange(false);
}}> }}>
({tempSelectedMembers.length}) ({tempSelectedMembers.length})
</Button> </Button>

View File

@@ -77,8 +77,8 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele
console.error("获取群聊列表失败:", error) console.error("获取群聊列表失败:", error)
showToast(error?.message || "请检查网络连接", "error") showToast(error?.message || "请检查网络连接", "error")
} finally { } finally {
setLoading(false) setLoading(false)
} }
} }
const handleSearch = () => { const handleSearch = () => {
@@ -105,14 +105,14 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele
</DialogHeader> </DialogHeader>
<div className="flex gap-2"> <div className="flex gap-2">
<div className="relative flex-1"> <div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" /> <Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input <Input
placeholder="搜索群聊" placeholder="搜索群聊"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()} onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="pl-9" className="pl-9"
/> />
</div> </div>
<Button <Button
variant="outline" variant="outline"

View File

@@ -31,4 +31,5 @@ export interface WechatGroupMember {
gender?: "male" | "female" gender?: "male" | "female"
role?: "owner" | "admin" | "member" role?: "owner" | "admin" | "member"
joinTime?: string joinTime?: string
groupId?: string
} }