diff --git a/nkebao/src/components/FriendSelection.tsx b/nkebao/src/components/FriendSelection.tsx index d35459cde..6e976dc7f 100644 --- a/nkebao/src/components/FriendSelection.tsx +++ b/nkebao/src/components/FriendSelection.tsx @@ -34,29 +34,35 @@ interface FriendsResponse { } // 获取好友列表API函数 -const fetchFriendsList = async (page: number = 1, limit: number = 20, deviceIds: string[]): Promise => { - if (deviceIds.length === 0) { +const fetchFriendsList = async (params: { + page: number; + limit: number; + deviceIds?: string[]; +}): Promise => { + if (params.deviceIds && params.deviceIds.length === 0) { return { code: 200, msg: 'success', data: { list: [], total: 0, - page, - limit + page: params.page, + limit: params.limit } }; } - const deviceIdsParam = deviceIds.join(','); - return get(`/v1/friend?page=${page}&limit=${limit}&deviceIds=${deviceIdsParam}`); + const deviceIdsParam = params?.deviceIds?.join(',') || '' + return get(`/v1/friend?page=${ params.page}&limit=${params.limit}&deviceIds=${deviceIdsParam}`); }; // 组件属性接口 interface FriendSelectionProps { selectedFriends: string[]; onSelect: (friends: string[]) => void; - deviceIds: string[]; + onSelectDetail?: (friends: WechatFriend[]) => void; // 新增 + deviceIds?: string[]; + enableDeviceFilter?: boolean; // 新增开关,默认true placeholder?: string; className?: string; } @@ -64,7 +70,9 @@ interface FriendSelectionProps { export default function FriendSelection({ selectedFriends, onSelect, - deviceIds, + onSelectDetail, + deviceIds = [], + enableDeviceFilter = true, placeholder = "选择微信好友", className = "" }: FriendSelectionProps) { @@ -76,27 +84,38 @@ export default function FriendSelection({ const [totalFriends, setTotalFriends] = useState(0); const [loading, setLoading] = useState(false); - // 当弹窗打开时获取好友列表 + // 打开弹窗并请求第一页好友 + const openDialog = () => { + setCurrentPage(1); + setDialogOpen(true); + fetchFriends(1); + }; + + // 当页码变化时,拉取对应页数据(弹窗已打开时) useEffect(() => { - if (dialogOpen && deviceIds.length > 0) { + if (dialogOpen && currentPage !== 1) { fetchFriends(currentPage); } - }, [dialogOpen, currentPage, deviceIds]); - - // 当设备ID变化时,重置页码 - useEffect(() => { - if (deviceIds.length > 0) { - setCurrentPage(1); - } - }, [deviceIds]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentPage]); // 获取好友列表API const fetchFriends = async (page: number) => { - if (deviceIds.length === 0) return; - setLoading(true); try { - const res = await fetchFriendsList(page, 20, deviceIds); + let res; + if (enableDeviceFilter) { + if (deviceIds.length === 0) { + setFriends([]); + setTotalFriends(0); + setTotalPages(1); + setLoading(false); + return; + } + res = await fetchFriendsList({ page, limit: 20, deviceIds: deviceIds }); + } else { + res = await fetchFriendsList({ page, limit: 20 }); + } if (res && res.code === 200 && res.data) { setFriends(res.data.list.map((friend) => ({ @@ -106,7 +125,6 @@ export default function FriendSelection({ avatar: friend.avatar || '', customer: friend.customer || '', }))); - setTotalFriends(res.data.total || 0); setTotalPages(Math.ceil((res.data.total || 0) / 20)); } @@ -125,10 +143,16 @@ export default function FriendSelection({ // 处理好友选择 const handleFriendToggle = (friendId: string) => { + let newIds: string[]; if (selectedFriends.includes(friendId)) { - onSelect(selectedFriends.filter(id => id !== friendId)); + newIds = selectedFriends.filter(id => id !== friendId); } else { - onSelect([...selectedFriends, friendId]); + newIds = [...selectedFriends, friendId]; + } + onSelect(newIds); + if (onSelectDetail) { + const selectedObjs = friends.filter(f => newIds.includes(f.id)); + onSelectDetail(selectedObjs); } }; @@ -155,7 +179,7 @@ export default function FriendSelection({ placeholder={placeholder} className="pl-10 h-12 rounded-xl border-gray-200 text-base" readOnly - onClick={() => setDialogOpen(true)} + onClick={openDialog} value={getDisplayText()} /> diff --git a/nkebao/src/components/GroupSelection.tsx b/nkebao/src/components/GroupSelection.tsx new file mode 100644 index 000000000..2ee6b4192 --- /dev/null +++ b/nkebao/src/components/GroupSelection.tsx @@ -0,0 +1,264 @@ +import React, { useState, useEffect } from 'react'; +import { Search, X } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; +import { get } from '@/api/request'; + +// 群组接口类型 +interface WechatGroup { + id: string; + chatroomId: string; + name: string; + avatar: string; + ownerWechatId: string; + ownerNickname: string; + ownerAvatar: string; +} + +interface GroupsResponse { + code: number; + msg: string; + data: { + list: Array<{ + id: number; + chatroomId: string; + name: string; + avatar?: string; + ownerWechatId?: string; + ownerNickname?: string; + ownerAvatar?: string; + }>; + total: number; + page: number; + limit: number; + }; +} + +const fetchGroupsList = async (params: { page: number; limit: number; }): Promise => { + return get(`/v1/chatroom?page=${params.page}&limit=${params.limit}`); +}; + +interface GroupSelectionProps { + selectedGroups: string[]; + onSelect: (groups: string[]) => void; + onSelectDetail?: (groups: WechatGroup[]) => void; // 新增 + placeholder?: string; + className?: string; +} + +export default function GroupSelection({ + selectedGroups, + onSelect, + onSelectDetail, + placeholder = '选择群聊', + className = '' +}: GroupSelectionProps) { + const [dialogOpen, setDialogOpen] = useState(false); + const [groups, setGroups] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalGroups, setTotalGroups] = useState(0); + const [loading, setLoading] = useState(false); + + // 打开弹窗并请求第一页群组 + const openDialog = () => { + setCurrentPage(1); + setDialogOpen(true); + fetchGroups(1); + }; + + // 当页码变化时,拉取对应页数据(弹窗已打开时) + useEffect(() => { + if (dialogOpen && currentPage !== 1) { + fetchGroups(currentPage); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentPage]); + + // 获取群组列表API + const fetchGroups = async (page: number) => { + setLoading(true); + try { + const res = await fetchGroupsList({ page, limit: 20 }); + if (res && res.code === 200 && res.data) { + setGroups(res.data.list.map((group) => ({ + id: group.id?.toString() || '', + chatroomId: group.chatroomId || '', + name: group.name || '', + avatar: group.avatar || '', + ownerWechatId: group.ownerWechatId || '', + ownerNickname: group.ownerNickname || '', + ownerAvatar: group.ownerAvatar || '', + }))); + setTotalGroups(res.data.total || 0); + setTotalPages(Math.ceil((res.data.total || 0) / 20)); + } + } catch (error) { + console.error('获取群组列表失败:', error); + } finally { + setLoading(false); + } + }; + + // 过滤群组 + const filteredGroups = groups.filter(group => + group.name.toLowerCase().includes(searchQuery.toLowerCase()) || + group.chatroomId.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // 处理群组选择 + const handleGroupToggle = (groupId: string) => { + let newIds: string[]; + if (selectedGroups.includes(groupId)) { + newIds = selectedGroups.filter(id => id !== groupId); + } else { + newIds = [...selectedGroups, groupId]; + } + onSelect(newIds); + if (onSelectDetail) { + const selectedObjs = groups.filter(g => newIds.includes(g.id)); + onSelectDetail(selectedObjs); + } + }; + + // 获取显示文本 + const getDisplayText = () => { + if (selectedGroups.length === 0) return ''; + return `已选择 ${selectedGroups.length} 个群聊`; + }; + + const handleConfirm = () => { + setDialogOpen(false); + }; + + return ( + <> + {/* 输入框 */} +
+ + + + + + +
+ + {/* 群组选择弹窗 */} + + +
+ 选择群聊 +
+ setSearchQuery(e.target.value)} + className="pl-10 py-2 rounded-full border-gray-200" + /> + + +
+
+ + + {loading ? ( +
+
加载中...
+
+ ) : filteredGroups.length > 0 ? ( +
+ {filteredGroups.map((group) => ( + + ))} +
+ ) : ( +
+
没有找到群聊
+
+ )} +
+ +
+
+ 总计 {totalGroups} 个群聊 +
+
+ + {currentPage} / {totalPages} + +
+
+ +
+ + +
+
+
+ + ); +} \ No newline at end of file diff --git a/nkebao/src/pages/content/NewContent.tsx b/nkebao/src/pages/content/NewContent.tsx index 540af0a19..d5d8c9c8c 100644 --- a/nkebao/src/pages/content/NewContent.tsx +++ b/nkebao/src/pages/content/NewContent.tsx @@ -2,15 +2,16 @@ import React, { useState, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import Layout from '@/components/Layout'; import UnifiedHeader from '@/components/UnifiedHeader'; -import BottomNav from '@/components/BottomNav'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Switch } from '@/components/ui/switch'; -import { Button } from '@/components/ui/button'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; import { Card } from '@/components/ui/card'; -import { Collapse, CollapsePanel } from 'tdesign-mobile-react'; +import { Collapse, CollapsePanel ,Button} from 'tdesign-mobile-react'; import { toast } from '@/components/ui/toast'; +import FriendSelection from '@/components/FriendSelection'; +import GroupSelection from '@/components/GroupSelection'; +import { post } from '@/api/request'; // TODO: 引入微信好友/群组选择器、日期选择器等组件 interface WechatFriend { id: string; nickname: string; avatar: string; } @@ -45,6 +46,8 @@ export default function NewContentLibraryPage() { aiPrompt: '', enabled: true, }); + const [selectedFriendObjs, setSelectedFriendObjs] = useState([]); + const [selectedGroupObjs, setSelectedGroupObjs] = useState([]); const [isFriendSelectorOpen, setIsFriendSelectorOpen] = useState(false); const [isGroupSelectorOpen, setIsGroupSelectorOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); @@ -54,7 +57,22 @@ export default function NewContentLibraryPage() { const handleSave = async () => { setIsSubmitting(true); try { - await new Promise((resolve) => setTimeout(resolve, 500)); + // 组装提交参数 + const payload = { + name: form.name, + sourceType: form.sourceType === 'friends' ? 1 : 2, + friends: form.selectedFriends.map(f => Number(f.id)), + groups: form.selectedGroups.map(g => Number(g.id)), + groupMembers: {}, + keywordInclude: form.keywordsInclude ? form.keywordsInclude.split(',').map(s => s.trim()).filter(Boolean) : [], + keywordExclude: form.keywordsExclude ? form.keywordsExclude.split(',').map(s => s.trim()).filter(Boolean) : [], + aiPrompt: form.aiPrompt, + timeEnabled: form.startDate || form.endDate ? 1 : 0, + startTime: form.startDate || '', + endTime: form.endDate || '', + status: form.enabled ? 1 : 0 + }; + await post('/v1/content/library/create', payload); toast({ title: '创建成功', description: '内容库已保存' }); navigate('/content'); } catch (error) { @@ -67,7 +85,13 @@ export default function NewContentLibraryPage() { return ( navigate(-1)} />} - footer={} + footer={ +
+ +
+ } >
@@ -90,28 +114,84 @@ export default function NewContentLibraryPage() { 选择聊天群 - - {form.selectedFriends.length > 0 && ( + f.id)} + onSelect={ids => setForm(f => ({ + ...f, + selectedFriends: ids.map(id => ({ id, nickname: id, avatar: '' })) + }))} + onSelectDetail={setSelectedFriendObjs} + enableDeviceFilter={false} + placeholder="选择微信好友" + /> + {selectedFriendObjs.length > 0 && (
- {form.selectedFriends.map(friend => ( + {selectedFriendObjs.map(friend => (
- {friend.nickname} +
+ {friend.avatar ? ( + {friend.nickname} + ) : ( +
{friend.nickname?.charAt(0) || '友'}
+ )} + {friend.nickname} +
+
))}
)}
- - {form.selectedGroups.length > 0 && ( + g.id)} + onSelect={ids => setForm(f => ({ + ...f, + selectedGroups: ids.map(id => { + const old = f.selectedGroups.find(g => g.id === id); + return old || { id, name: id, avatar: '' }; + }) + }))} + onSelectDetail={setSelectedGroupObjs} + placeholder="选择群聊" + /> + {selectedGroupObjs.length > 0 && (
- {form.selectedGroups.map(group => ( + {selectedGroupObjs.map(group => (
- {group.name} +
+ {group.avatar ? ( + {group.name} + ) : ( +
{group.name?.charAt(0) || '群'}
+ )} + {group.name} +
+
))}
@@ -187,11 +267,7 @@ export default function NewContentLibraryPage() { setForm(f => ({ ...f, enabled: checked }))} />
-
- -
+
diff --git a/nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx b/nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx index 59796bd23..67ca335db 100644 --- a/nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx +++ b/nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx @@ -52,6 +52,7 @@ function formatDate(dateString: string) { export default function TrafficPoolDetail() { const { id } = useParams(); + const [activeTab, setActiveTab] = useState<'base' | 'journey' | 'tags'>('base'); const user = getUserById(id as string); if (!user) { return
未找到该用户
; @@ -61,7 +62,6 @@ export default function TrafficPoolDetail() { const device = getDevice(user.deviceId); // RFM分段 const rfmSegment = Object.values(RFM_SEGMENTS).find((seg: any) => seg.name === user.rfmScore.segment) as { name: string; color: string } | undefined; - const [activeTab, setActiveTab] = useState<'base' | 'journey' | 'tags'>('base'); return (