diff --git a/nkebao/src/components/ui/avatar.tsx b/nkebao/src/components/ui/avatar.tsx new file mode 100644 index 00000000..0273e3af --- /dev/null +++ b/nkebao/src/components/ui/avatar.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +interface AvatarProps { + children: React.ReactNode; + className?: string; +} + +export function Avatar({ children, className = '' }: AvatarProps) { + return ( +
+ {children} +
+ ); +} + +interface AvatarImageProps { + src?: string; + alt?: string; + className?: string; +} + +export function AvatarImage({ src, alt, className = '' }: AvatarImageProps) { + if (!src) return null; + + return ( + {alt + ); +} + +interface AvatarFallbackProps { + children: React.ReactNode; + className?: string; +} + +export function AvatarFallback({ children, className = '' }: AvatarFallbackProps) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/nkebao/src/components/ui/badge.tsx b/nkebao/src/components/ui/badge.tsx index 73aec328..15d38737 100644 --- a/nkebao/src/components/ui/badge.tsx +++ b/nkebao/src/components/ui/badge.tsx @@ -2,7 +2,7 @@ import React from 'react'; interface BadgeProps { children: React.ReactNode; - variant?: 'default' | 'secondary' | 'success' | 'destructive'; + variant?: 'default' | 'secondary' | 'success' | 'destructive' | 'outline'; className?: string; onClick?: (e: React.MouseEvent) => void; } @@ -19,7 +19,8 @@ export function Badge({ default: 'bg-blue-100 text-blue-800', secondary: 'bg-gray-100 text-gray-800', success: 'bg-green-100 text-green-800', - destructive: 'bg-red-100 text-red-800' + destructive: 'bg-red-100 text-red-800', + outline: 'border border-gray-300 bg-white text-gray-700' }; const classes = `${baseClasses} ${variantClasses[variant]} ${className}`; diff --git a/nkebao/src/components/ui/dropdown-menu.tsx b/nkebao/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..7ccd5fa5 --- /dev/null +++ b/nkebao/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,109 @@ +import React, { useState, useRef, useEffect } from 'react'; + +interface DropdownMenuProps { + children: React.ReactNode; +} + +export function DropdownMenu({ children }: DropdownMenuProps) { + return <>{children}; +} + +interface DropdownMenuTriggerProps { + children: React.ReactNode; + asChild?: boolean; +} + +export function DropdownMenuTrigger({ children }: DropdownMenuTriggerProps) { + return <>{children}; +} + +interface DropdownMenuContentProps { + children: React.ReactNode; + align?: 'start' | 'center' | 'end'; +} + +export function DropdownMenuContent({ children, align = 'end' }: DropdownMenuContentProps) { + const [isOpen, setIsOpen] = useState(false); + const triggerRef = useRef(null); + const contentRef = useRef(null); + + useEffect(() => { + const trigger = triggerRef.current; + if (!trigger) return; + + const handleClick = () => setIsOpen(!isOpen); + trigger.addEventListener('click', handleClick); + + return () => { + trigger.removeEventListener('click', handleClick); + }; + }, [isOpen]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + contentRef.current && + !contentRef.current.contains(event.target as Node) && + triggerRef.current && + !triggerRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
+
+ {React.Children.map(children, (child) => { + if (React.isValidElement(child)) { + return React.cloneElement(child, { + ...child.props, + children: ( + <> + {child.props.children} + {isOpen && ( +
+ {children} +
+ )} + + ) + }); + } + return child; + })} +
+
+ ); +} + +interface DropdownMenuItemProps { + children: React.ReactNode; + onClick?: () => void; + disabled?: boolean; +} + +export function DropdownMenuItem({ children, onClick, disabled = false }: DropdownMenuItemProps) { + return ( + + ); +} \ No newline at end of file diff --git a/nkebao/src/components/ui/input.tsx b/nkebao/src/components/ui/input.tsx index 104635ef..9bc18656 100644 --- a/nkebao/src/components/ui/input.tsx +++ b/nkebao/src/components/ui/input.tsx @@ -3,6 +3,7 @@ import React from 'react'; interface InputProps { value?: string; onChange?: (e: React.ChangeEvent) => void; + onKeyDown?: (e: React.KeyboardEvent) => void; placeholder?: string; className?: string; readOnly?: boolean; @@ -12,6 +13,7 @@ interface InputProps { export function Input({ value, onChange, + onKeyDown, placeholder, className = '', readOnly = false, @@ -23,6 +25,7 @@ export function Input({ type="text" value={value} onChange={onChange} + onKeyDown={onKeyDown} placeholder={placeholder} readOnly={readOnly} className={`flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}`} diff --git a/nkebao/src/components/ui/progress.tsx b/nkebao/src/components/ui/progress.tsx new file mode 100644 index 00000000..8001b9e1 --- /dev/null +++ b/nkebao/src/components/ui/progress.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +interface ProgressProps { + value: number; + className?: string; +} + +export function Progress({ value, className = '' }: ProgressProps) { + return ( +
+
+
+ ); +} \ No newline at end of file diff --git a/nkebao/src/components/ui/switch.tsx b/nkebao/src/components/ui/switch.tsx new file mode 100644 index 00000000..0b2a85f7 --- /dev/null +++ b/nkebao/src/components/ui/switch.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +interface SwitchProps { + checked: boolean; + onCheckedChange: (checked: boolean) => void; + disabled?: boolean; + className?: string; +} + +export function Switch({ checked, onCheckedChange, disabled = false, className = '' }: SwitchProps) { + return ( + + ); +} \ No newline at end of file diff --git a/nkebao/src/components/ui/tabs.tsx b/nkebao/src/components/ui/tabs.tsx new file mode 100644 index 00000000..8e6fe8b1 --- /dev/null +++ b/nkebao/src/components/ui/tabs.tsx @@ -0,0 +1,52 @@ +import * as React from "react"; +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import { cn } from "@/utils"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; \ No newline at end of file diff --git a/nkebao/src/pages/content/Content.tsx b/nkebao/src/pages/content/Content.tsx index 7629b07a..1c7d6a6b 100644 --- a/nkebao/src/pages/content/Content.tsx +++ b/nkebao/src/pages/content/Content.tsx @@ -1,5 +1,350 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ChevronLeft, Filter, Search, RefreshCw, Plus, Edit, Trash2, Eye, MoreVertical } from 'lucide-react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Badge } from '@/components/ui/badge'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { useToast } from '@/components/ui/toast'; +import { get, del } from '@/api/request'; + +interface ApiResponse { + code: number; + msg: string; + data: T; +} + +interface LibraryListResponse { + list: ContentLibrary[]; + total: number; +} + +interface WechatGroupMember { + id: string; + nickname: string; + wechatId: string; + avatar: string; + gender?: 'male' | 'female'; + role?: 'owner' | 'admin' | 'member'; + joinTime?: string; +} + +interface ContentLibrary { + id: string; + name: string; + source: 'friends' | 'groups'; + targetAudience: { + id: string; + nickname: string; + avatar: string; + }[]; + creator: string; + creatorName?: string; + itemCount: number; + lastUpdated: string; + enabled: boolean; + sourceFriends: string[]; + sourceGroups: string[]; + friendsData?: any[]; + groupsData?: any[]; + keywordInclude: string[]; + keywordExclude: string[]; + isEnabled: number; + aiPrompt: string; + timeEnabled: number; + timeStart: string; + timeEnd: string; + status: number; + createTime: string; + updateTime: string; + sourceType: number; + selectedGroupMembers?: WechatGroupMember[]; +} export default function Content() { - return
内容管理页
; + const navigate = useNavigate(); + const [libraries, setLibraries] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const [activeTab, setActiveTab] = useState('all'); + const [loading, setLoading] = useState(false); + const { toast } = useToast(); + + // 获取内容库列表 + const fetchLibraries = useCallback(async () => { + setLoading(true); + try { + const queryParams = new URLSearchParams({ + page: '1', + limit: '100', + ...(searchQuery ? { keyword: searchQuery } : {}), + ...(activeTab !== 'all' ? { sourceType: activeTab === 'friends' ? '1' : '2' } : {}) + }); + const response = await get>(`/v1/content/library/list?${queryParams.toString()}`); + + if (response.code === 200 && response.data) { + // 转换数据格式以匹配原有UI + const transformedLibraries = response.data.list.map((item: any) => { + const friendsData = Array.isArray(item.selectedFriends) ? item.selectedFriends : []; + const groupsData = Array.isArray(item.selectedGroups) ? item.selectedGroups : []; + + const transformedItem: ContentLibrary = { + id: item.id, + name: item.name, + source: item.sourceType === 1 ? 'friends' : 'groups', + targetAudience: [ + ...friendsData.map((friend: any) => ({ + id: friend.id, + nickname: friend.nickname || `好友${friend.id}`, + avatar: friend.avatar || '/placeholder.svg' + })), + ...groupsData.map((group: any) => ({ + id: group.id, + nickname: group.name || `群组${group.id}`, + avatar: group.avatar || '/placeholder.svg' + })) + ], + creator: item.creatorName || '系统', + creatorName: item.creatorName, + itemCount: item.itemCount, + lastUpdated: item.updateTime, + enabled: item.isEnabled === 1, + sourceFriends: item.sourceFriends || [], + sourceGroups: item.sourceGroups || [], + friendsData: friendsData, + groupsData: groupsData, + keywordInclude: item.keywordInclude || [], + keywordExclude: item.keywordExclude || [], + isEnabled: item.isEnabled, + aiPrompt: item.aiPrompt || '', + timeEnabled: item.timeEnabled, + timeStart: item.timeStart || '', + timeEnd: item.timeEnd || '', + status: item.status, + createTime: item.createTime, + updateTime: item.updateTime, + sourceType: item.sourceType, + selectedGroupMembers: item.selectedGroupMembers || [] + }; + return transformedItem; + }); + setLibraries(transformedLibraries); + } else { + toast({ title: '获取失败', description: response.msg || '获取内容库列表失败' }); + } + } catch (error: any) { + console.error('获取内容库列表失败:', error); + toast({ title: '网络错误', description: error?.message || '请检查网络连接' }); + } finally { + setLoading(false); + } + }, [searchQuery, activeTab, toast]); + + useEffect(() => { + fetchLibraries(); + }, [fetchLibraries]); + + const handleCreateNew = () => { + navigate('/content/new'); + }; + + const handleEdit = (id: string) => { + navigate(`/content/${id}/edit`); + }; + + const handleDelete = async (id: string) => { + try { + const response = await del(`/v1/content/library/delete?id=${id}`); + if (response.code === 200) { + toast({ title: '删除成功', description: '内容库已删除' }); + fetchLibraries(); + } else { + toast({ title: '删除失败', description: response.msg || '删除失败' }); + } + } catch (error: any) { + console.error('删除内容库失败:', error); + toast({ title: '网络错误', description: error?.message || '请检查网络连接' }); + } + }; + + const handleViewMaterials = (id: string) => { + navigate(`/content/${id}/materials`); + }; + + const handleSearch = () => { + fetchLibraries(); + }; + + const handleRefresh = () => { + fetchLibraries(); + }; + + const filteredLibraries = libraries.filter( + (library) => + library.name.toLowerCase().includes(searchQuery.toLowerCase()) || + library.targetAudience.some((target) => target.nickname.toLowerCase().includes(searchQuery.toLowerCase())) + ); + + return ( +
+
+
+
+ +

内容库

+
+ +
+
+ +
+ +
+
+
+ + setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="pl-9" + /> +
+ + +
+ + + + 全部 + 微信好友 + 聊天群 + + + +
+ {loading ? ( +
+ +
+ ) : filteredLibraries.length === 0 ? ( +
+
+

暂无数据

+ +
+
+ ) : ( + filteredLibraries.map((library) => ( + +
+
+
+

{library.name}

+ + {library.isEnabled === 1 ? '已启用' : '未启用'} + +
+
+
+ 来源: + {library.sourceType === 1 && library.sourceFriends?.length > 0 ? ( +
+ {(library.friendsData || []).slice(0, 3).map((friend) => ( + {friend.nickname + ))} + {library.sourceFriends.length > 3 && ( + + +{library.sourceFriends.length - 3} + + )} +
+ ) : library.sourceType === 2 && library.sourceGroups?.length > 0 ? ( +
+
+ {(library.groupsData || []).slice(0, 3).map((group) => ( + {group.name + ))} + {library.sourceGroups.length > 3 && ( + + +{library.sourceGroups.length - 3} + + )} +
+
+ ) : ( +
+ )} +
+
创建人:{library.creator}
+
内容数量:{library.itemCount}
+
更新时间:{new Date(library.updateTime).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + })}
+
+
+ + + + + + handleEdit(library.id)}> + + 编辑 + + handleDelete(library.id)}> + + 删除 + + handleViewMaterials(library.id)}> + + 查看素材 + + + +
+
+ )) + )} +
+
+
+
+
+ ); } \ No newline at end of file diff --git a/nkebao/src/pages/profile/Profile.tsx b/nkebao/src/pages/profile/Profile.tsx index 870ce1c9..46f06979 100644 --- a/nkebao/src/pages/profile/Profile.tsx +++ b/nkebao/src/pages/profile/Profile.tsx @@ -1,67 +1,50 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ChevronRight, Settings, Bell, LogOut } from 'lucide-react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { useAuth } from '@/contexts/AuthContext'; import { useToast } from '@/components/ui/toast'; -import { LogOut, User, Settings, Shield, Bell } from 'lucide-react'; + +const menuItems = [ + { href: '/devices', label: '设备管理' }, + { href: '/wechat-accounts', label: '微信号管理' }, + { href: '/traffic-pool', label: '流量池' }, + { href: '/content', label: '内容库' }, +]; export default function Profile() { + const navigate = useNavigate(); const { user, logout, isAuthenticated } = useAuth(); const { toast } = useToast(); + const [showLogoutDialog, setShowLogoutDialog] = useState(false); + const [userInfo, setUserInfo] = useState(null); + + // 从localStorage获取用户信息 + useEffect(() => { + const userInfoStr = localStorage.getItem('userInfo'); + if (userInfoStr) { + setUserInfo(JSON.parse(userInfoStr)); + } + }, []); const handleLogout = () => { + // 清除本地存储的用户信息 + localStorage.removeItem('token'); + localStorage.removeItem('token_expired'); + localStorage.removeItem('s2_accountId'); + localStorage.removeItem('userInfo'); + setShowLogoutDialog(false); logout(); + navigate('/login'); toast({ title: '已退出登录', description: '感谢使用存客宝', }); }; - const menuItems = [ - { - icon: , - title: '个人信息', - description: '查看和编辑个人资料', - onClick: () => { - toast({ - title: '功能开发中', - description: '个人信息编辑功能正在开发中', - }); - } - }, - { - icon: , - title: '账户设置', - description: '密码、安全设置等', - onClick: () => { - toast({ - title: '功能开发中', - description: '账户设置功能正在开发中', - }); - } - }, - { - icon: , - title: '隐私安全', - description: '隐私设置和安全选项', - onClick: () => { - toast({ - title: '功能开发中', - description: '隐私安全功能正在开发中', - }); - } - }, - { - icon: , - title: '消息通知', - description: '通知设置和消息管理', - onClick: () => { - toast({ - title: '功能开发中', - description: '消息通知功能正在开发中', - }); - } - } - ]; - if (!isAuthenticated) { return (
@@ -71,83 +54,102 @@ export default function Profile() { } return ( -
-
- {/* 用户信息卡片 */} -
-
-
- {user?.avatar ? ( - 头像 - ) : ( - - )} -
-
-

- {user?.username || '用户'} -

-

- {user?.account || '暂无手机号'} -

- {user?.s2_accountId && ( -

- ID: {user.s2_accountId} -

- )} -
+
+
+
+

我的

+
+ +
+
- {/* 菜单列表 */} -
- {menuItems.map((item, index) => ( -
- - {index < menuItems.length - 1 && ( -
- )} +
+ {/* 用户信息卡片 */} + +
+ + + + {(userInfo?.username || user?.username || '用户').slice(0, 2)} + + +
+

+ {userInfo?.username || user?.username || '用户'} +

+

+ 账号: {userInfo?.account || user?.account || Math.floor(10000000 + Math.random() * 90000000).toString()} +

+
+ +
+
+
+
+ + {/* 功能菜单 */} + + {menuItems.map((item) => ( +
(item.href ? navigate(item.href) : null)} + > +
+ {item.label} +
+
))} -
+ {/* 退出登录按钮 */} -
- -
- - {/* 版本信息 */} -
-

存客宝 v1.0.0

-

© 2024 存客宝. All rights reserved.

-
+
+ + {/* 退出登录确认对话框 */} + + + + 确认退出登录 + + 您确定要退出登录吗?退出后需要重新登录才能使用完整功能。 + + +
+ + +
+
+
); } \ No newline at end of file diff --git a/nkebao/src/pages/traffic-pool/TrafficPool.tsx b/nkebao/src/pages/traffic-pool/TrafficPool.tsx index ef9a94fe..9b7d833a 100644 --- a/nkebao/src/pages/traffic-pool/TrafficPool.tsx +++ b/nkebao/src/pages/traffic-pool/TrafficPool.tsx @@ -1,5 +1,173 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Search, RefreshCw } from 'lucide-react'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { useToast } from '@/components/ui/toast'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Badge } from '@/components/ui/badge'; +import { get } from '@/api/request'; + +interface UserTag { + id: string; + name: string; + color: string; +} + +interface TrafficUser { + id: string; + avatar: string; + nickname: string; + wechatId: string; + phone: string; + region: string; + note: string; + status: number; + addTime: string; + source: string; + assignedTo: string; + category: 'potential' | 'customer' | 'lost'; + tags: UserTag[]; +} + +interface ApiResponse { + code: number; + msg: string; + data: T; +} + +interface TrafficPoolResponse { + list: TrafficUser[]; + pagination: { + total: number; + current: number; + pageSize: number; + totalPages: number; + }; +} export default function TrafficPool() { - return
流量池页
; + const [users, setUsers] = useState([]); + const [activeCategory, setActiveCategory] = useState('potential'); + const [searchQuery, setSearchQuery] = useState(''); + + // 格式化时间 + const formatDateTime = (dateString: string) => { + if (!dateString) return '--'; + try { + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false + }).replace(/\//g, '-'); + } catch (error) { + return dateString; + } + }; + + const { toast } = useToast(); + + // 获取流量池用户 + const fetchUsers = useCallback(async () => { + try { + const params = new URLSearchParams({ + page: '1', + limit: '30', + ...(searchQuery ? { keyword: searchQuery } : {}), + }); + const endpoint = activeCategory === 'customer' ? '/v1/traffic/pool/converted' : '/v1/traffic/pool'; + const response = await get>(`${endpoint}?${params.toString()}`); + if (response.code === 200 && response.data) { + setUsers(response.data.list); + } else { + toast({ title: '获取失败', description: response.msg || '获取流量池失败' }); + } + } catch (error: any) { + toast({ title: '网络错误', description: error?.message || '请检查网络连接' }); + } + }, [activeCategory, searchQuery, toast]); + + useEffect(() => { + fetchUsers(); + }, [fetchUsers]); + + return ( +
+
+
+

流量池

+ +
+
+
+
+ setSearchQuery(e.target.value)} + className="w-64" + /> + +
+ + + 潜在客户 + 已转化 + 已流失 + + + +
+ + + + + + + + + + + + + + + {users.map(user => ( + + + + + + + + + + + ))} + {users.length === 0 && ( + + + + )} + +
头像昵称微信号手机号地区标签备注添加时间
+ + + {user.nickname?.slice(0, 2) || '用户'} + + {user.nickname}{user.wechatId}{user.phone}{user.region} + {user.tags?.map(tag => ( + {tag.name} + ))} + {user.note}{formatDateTime(user.addTime)}
暂无数据
+
+
+
+
+ ); } \ No newline at end of file diff --git a/nkebao/src/pages/workspace/Workspace.tsx b/nkebao/src/pages/workspace/Workspace.tsx index cb60a38a..7f7fb6dc 100644 --- a/nkebao/src/pages/workspace/Workspace.tsx +++ b/nkebao/src/pages/workspace/Workspace.tsx @@ -1,5 +1,197 @@ import React from 'react'; +import { Link } from 'react-router-dom'; +import { ThumbsUp, MessageSquare, Send, Users, Share2, Brain, BarChart2, LineChart, Clock } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; export default function Workspace() { - return
工作台首页
; + // 模拟任务数据 + const taskStats = { + total: 42, + inProgress: 12, + completed: 30, + todayTasks: 12, + activityRate: 98, + }; + + // 常用功能 - 保持原有排列 + const commonFeatures = [ + { + id: "auto-like", + name: "自动点赞", + description: "智能自动点赞互动", + icon: , + path: "/workspace/auto-like", + bgColor: "bg-red-100", + isNew: true, + }, + { + id: "moments-sync", + name: "朋友圈同步", + description: "自动同步朋友圈内容", + icon: , + path: "/workspace/moments-sync", + bgColor: "bg-purple-100", + }, + { + id: "group-push", + name: "群消息推送", + description: "智能群发助手", + icon: , + path: "/workspace/group-push", + bgColor: "bg-orange-100", + }, + { + id: "auto-group", + name: "自动建群", + description: "智能拉好友建群", + icon: , + path: "/workspace/auto-group", + bgColor: "bg-green-100", + }, + { + id: "traffic-distribution", + name: "流量分发", + description: "管理流量分发和分配", + icon: , + path: "/workspace/traffic-distribution", + bgColor: "bg-blue-100", + }, + { + id: "ai-assistant", + name: "AI对话助手", + description: "智能回复,提高互动质量", + icon: , + path: "/workspace/ai-assistant", + bgColor: "bg-blue-100", + isNew: true, + }, + ]; + + // AI智能助手 + const aiFeatures = [ + { + id: "ai-analyzer", + name: "AI数据分析", + description: "智能分析客户行为特征", + icon: , + path: "/workspace/ai-analyzer", + bgColor: "bg-indigo-100", + isNew: true, + }, + { + id: "ai-strategy", + name: "AI策略优化", + description: "智能优化获客策略", + icon: , + path: "/workspace/ai-strategy", + bgColor: "bg-cyan-100", + isNew: true, + }, + { + id: "ai-forecast", + name: "AI销售预测", + description: "智能预测销售趋势", + icon: , + path: "/workspace/ai-forecast", + bgColor: "bg-amber-100", + }, + ]; + + return ( +
+
+

工作台

+ + {/* 任务统计卡片 */} +
+ + +
总任务数
+
{taskStats.total}
+ +
+ 进行中: {taskStats.inProgress} / 已完成: {taskStats.completed} +
+
+
+ + + +
今日任务
+
{taskStats.todayTasks}
+
+ + + + 活跃度 {taskStats.activityRate}% +
+
+
+
+ + {/* 常用功能 */} +
+

常用功能

+
+ {commonFeatures.map((feature) => ( + + + +
+ {feature.icon} +
+
+
{feature.name}
+ {feature.isNew && ( + New + )} +
+
{feature.description}
+
+
+ + ))} +
+
+ + {/* AI智能助手 */} +
+

AI 智能助手

+
+ {aiFeatures.map((feature) => ( + + + +
+ {feature.icon} +
+
+
{feature.name}
+ {feature.isNew && ( + New + )} +
+
{feature.description}
+
+
+ + ))} +
+
+
+
+ ); } \ No newline at end of file diff --git a/nkebao/src/pages/workspace/auto-group/AutoGroup.tsx b/nkebao/src/pages/workspace/auto-group/AutoGroup.tsx new file mode 100644 index 00000000..c3500cb4 --- /dev/null +++ b/nkebao/src/pages/workspace/auto-group/AutoGroup.tsx @@ -0,0 +1,352 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import { Plus, Filter, Search, RefreshCw, MoreVertical, Users, Edit, Trash2, Eye, Copy } from 'lucide-react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { Switch } from '@/components/ui/switch'; +import PageHeader from '@/components/PageHeader'; +import { useToast } from '@/components/ui/toast'; + +interface GroupTask { + id: string; + name: string; + status: number; // 1-运行中,0-暂停 + deviceCount: number; + groupCount: number; + memberCount: number; + lastCreateTime: string; + createTime: string; + creator: string; + creatorName: string; + config: { + devices: string[]; + targetGroups: string[]; + maxMembersPerGroup: number; + }; +} + +export default function AutoGroup() { + const navigate = useNavigate(); + const { toast } = useToast(); + const [tasks, setTasks] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [total, setTotal] = useState(0); + + // 模拟数据 + const mockTasks: GroupTask[] = [ + { + id: '1', + name: '自动建群任务1', + status: 1, + deviceCount: 3, + groupCount: 15, + memberCount: 450, + lastCreateTime: '2024-03-18 16:30:00', + createTime: '2024-03-15 10:00:00', + creator: 'admin', + creatorName: '管理员', + config: { + devices: ['device1', 'device2', 'device3'], + targetGroups: ['VIP客户', '活跃用户'], + maxMembersPerGroup: 30 + } + }, + { + id: '2', + name: '自动建群任务2', + status: 0, + deviceCount: 2, + groupCount: 8, + memberCount: 240, + lastCreateTime: '2024-03-17 14:20:00', + createTime: '2024-03-14 15:30:00', + creator: 'user1', + creatorName: '用户1', + config: { + devices: ['device4', 'device5'], + targetGroups: ['新客户'], + maxMembersPerGroup: 25 + } + } + ]; + + // 获取任务列表 + const fetchTasks = async () => { + setIsLoading(true); + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 1000)); + + // 模拟搜索过滤 + let filteredTasks = mockTasks; + if (searchQuery) { + filteredTasks = mockTasks.filter(task => + task.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + + setTasks(filteredTasks); + setTotal(filteredTasks.length); + } catch (error: any) { + console.error('获取自动建群任务列表失败:', error); + toast({ + title: '获取失败', + description: error?.message || '请检查网络连接', + }); + } finally { + setIsLoading(false); + } + }; + + // 组件加载时获取任务列表 + useEffect(() => { + fetchTasks(); + }, [currentPage, pageSize]); + + // 处理页码变化 + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + // 搜索任务 + const handleSearch = () => { + fetchTasks(); + }; + + // 切换任务状态 + const toggleTaskStatus = async (taskId: string, currentStatus: number) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + const newStatus = currentStatus === 1 ? 0 : 1; + setTasks( + tasks.map((task) => + task.id === taskId ? { ...task, status: newStatus } : task + ) + ); + + toast({ + title: '状态更新成功', + description: `任务已${newStatus === 1 ? '启用' : '暂停'}`, + }); + } catch (error: any) { + console.error('更新任务状态失败:', error); + toast({ + title: '更新失败', + description: error?.message || '更新任务状态失败', + }); + } + }; + + // 执行删除 + const handleDelete = async (taskId: string) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + setTasks(tasks.filter((task) => task.id !== taskId)); + toast({ + title: '删除成功', + description: '已成功删除建群任务', + }); + } catch (error: any) { + console.error('删除任务失败:', error); + toast({ + title: '删除失败', + description: error?.message || '删除任务失败', + }); + } + }; + + // 编辑任务 + const handleEdit = (taskId: string) => { + navigate(`/workspace/auto-group/${taskId}/edit`); + }; + + // 查看任务详情 + const handleView = (taskId: string) => { + navigate(`/workspace/auto-group/${taskId}`); + }; + + // 复制任务 + const handleCopy = async (taskId: string) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + const taskToCopy = tasks.find(task => task.id === taskId); + if (taskToCopy) { + const newTask = { + ...taskToCopy, + id: Date.now().toString(), + name: `${taskToCopy.name} (副本)`, + status: 0, + createTime: new Date().toLocaleString(), + groupCount: 0, + memberCount: 0, + lastCreateTime: '-' + }; + setTasks([newTask, ...tasks]); + toast({ + title: '复制成功', + description: '已成功复制建群任务', + }); + } + } catch (error: any) { + console.error('复制任务失败:', error); + toast({ + title: '复制失败', + description: error?.message || '复制任务失败', + }); + } + }; + + // 过滤任务 + const filteredTasks = tasks.filter( + (task) => task.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return ( +
+ + + + } + /> + +
+ +
+
+ + setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + /> +
+ + +
+
+ +
+ {isLoading ? ( +
加载中...
+ ) : filteredTasks.length > 0 ? ( + filteredTasks.map((task) => ( + +
+
+

{task.name}

+ + {task.status === 1 ? '运行中' : '已暂停'} + +
+
+ toggleTaskStatus(task.id, task.status)} /> + + + + + + handleView(task.id)}> + + 查看 + + handleEdit(task.id)}> + + 编辑 + + handleCopy(task.id)}> + + 复制 + + handleDelete(task.id)}> + + 删除 + + + +
+
+ +
+
+
执行设备:{task.deviceCount} 个
+
已建群数:{task.groupCount} 个
+
创建者:{task.creatorName}
+
+
+
群成员数:{task.memberCount} 人
+
最后建群:{task.lastCreateTime}
+
创建时间:{task.createTime}
+
+
+ +
+
+ + 目标人群:{task.config.targetGroups.join(', ')} +
+
+
+ )) + ) : ( +
+ {searchQuery ? '没有找到匹配的任务' : '暂无建群任务'} +
+ )} +
+ + {/* 分页 */} + {total > pageSize && ( +
+ +
+ 第 {currentPage} 页 + 共 {Math.ceil(total / pageSize)} 页 +
+ +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/nkebao/src/pages/workspace/auto-like/AutoLike.tsx b/nkebao/src/pages/workspace/auto-like/AutoLike.tsx new file mode 100644 index 00000000..9de1ba0b --- /dev/null +++ b/nkebao/src/pages/workspace/auto-like/AutoLike.tsx @@ -0,0 +1,494 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import { + Plus, + Filter, + Search, + RefreshCw, + MoreVertical, + Edit, + Trash2, + Eye, + Copy, + Settings, + Users, + ThumbsUp, +} from 'lucide-react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { Switch } from '@/components/ui/switch'; +import { Progress } from '@/components/ui/progress'; +import PageHeader from '@/components/PageHeader'; +import { useToast } from '@/components/ui/toast'; + +interface TaskConfig { + id: number; + workbenchId: number; + interval: number; + maxLikes: number; + friendMaxLikes?: number; + startTime: string; + endTime: string; + contentTypes: string[]; + devices: number[]; + targetGroups: string[]; + tagOperator: number; + createTime: string; + updateTime: string; + todayLikeCount?: number; + totalLikeCount?: number; + friends?: string[]; + enableFriendTags?: boolean; + friendTags?: string; +} + +interface Task { + id: number; + name: string; + type: number; + status: number; + autoStart: number; + createTime: string; + updateTime: string; + config: TaskConfig; +} + +interface TaskListResponse { + code: number; + msg: string; + data: { + list: Task[]; + total: number; + }; +} + +interface ApiResponse { + code: number; + msg: string; +} + +export default function AutoLike() { + const navigate = useNavigate(); + const { toast } = useToast(); + const [expandedTaskId, setExpandedTaskId] = useState(null); + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [searchName, setSearchName] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [total, setTotal] = useState(0); + const pageSize = 10; + + // 模拟数据 + const mockTasks: Task[] = [ + { + id: 1, + name: '智能点赞任务1', + type: 1, + status: 1, + autoStart: 1, + createTime: '2024-03-18 10:00:00', + updateTime: '2024-03-18 16:30:00', + config: { + id: 1, + workbenchId: 1, + interval: 30, + maxLikes: 100, + friendMaxLikes: 3, + startTime: '09:00', + endTime: '18:00', + contentTypes: ['text', 'image'], + devices: [1, 2, 3], + targetGroups: ['VIP客户', '活跃用户'], + tagOperator: 1, + createTime: '2024-03-18 10:00:00', + updateTime: '2024-03-18 16:30:00', + todayLikeCount: 45, + totalLikeCount: 1234, + friends: ['friend1', 'friend2', 'friend3'], + enableFriendTags: true, + friendTags: '重要客户' + } + }, + { + id: 2, + name: '自动点赞任务2', + type: 1, + status: 2, + autoStart: 0, + createTime: '2024-03-17 14:20:00', + updateTime: '2024-03-18 12:15:00', + config: { + id: 2, + workbenchId: 2, + interval: 60, + maxLikes: 50, + friendMaxLikes: 2, + startTime: '10:00', + endTime: '20:00', + contentTypes: ['video'], + devices: [4, 5], + targetGroups: ['新客户'], + tagOperator: 2, + createTime: '2024-03-17 14:20:00', + updateTime: '2024-03-18 12:15:00', + todayLikeCount: 0, + totalLikeCount: 567, + friends: ['friend4', 'friend5'], + enableFriendTags: false + } + } + ]; + + const fetchTasks = async (page: number, name?: string) => { + setLoading(true); + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 1000)); + + // 模拟搜索过滤 + let filteredTasks = mockTasks; + if (name) { + filteredTasks = mockTasks.filter(task => + task.name.toLowerCase().includes(name.toLowerCase()) + ); + } + + setTasks(filteredTasks); + setTotal(filteredTasks.length); + } catch (error: any) { + console.error('获取任务列表失败:', error); + toast({ + title: '获取失败', + description: error?.message || '请检查网络连接', + }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchTasks(currentPage, searchName); + }, [currentPage]); + + const handleSearch = () => { + setCurrentPage(1); + fetchTasks(1, searchName); + }; + + const handleRefresh = () => { + fetchTasks(currentPage, searchName); + }; + + const toggleExpand = (taskId: number) => { + setExpandedTaskId(expandedTaskId === taskId ? null : taskId); + }; + + const handleDelete = async (taskId: number) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + setTasks(tasks.filter(task => task.id !== taskId)); + toast({ + title: '删除成功', + description: '已成功删除点赞任务', + }); + } catch (error: any) { + console.error('删除任务失败:', error); + toast({ + title: '删除失败', + description: error?.message || '请检查网络连接', + }); + } + }; + + const handleEdit = (taskId: number) => { + navigate(`/workspace/auto-like/${taskId}/edit`); + }; + + const handleView = (taskId: number) => { + navigate(`/workspace/auto-like/${taskId}`); + }; + + const handleCopy = async (taskId: number) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + const taskToCopy = tasks.find(task => task.id === taskId); + if (taskToCopy) { + const newTask = { + ...taskToCopy, + id: Date.now(), + name: `${taskToCopy.name} (副本)`, + status: 2, + createTime: new Date().toLocaleString(), + updateTime: new Date().toLocaleString(), + config: { + ...taskToCopy.config, + id: Date.now(), + workbenchId: Date.now(), + todayLikeCount: 0, + totalLikeCount: 0, + createTime: new Date().toLocaleString(), + updateTime: new Date().toLocaleString(), + } + }; + setTasks([newTask, ...tasks]); + toast({ + title: '复制成功', + description: '已成功复制点赞任务', + }); + } + } catch (error: any) { + console.error('复制任务失败:', error); + toast({ + title: '复制失败', + description: error?.message || '请检查网络连接', + }); + } + }; + + const toggleTaskStatus = async (taskId: number, currentStatus: number) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + const newStatus = currentStatus === 1 ? 2 : 1; + setTasks(tasks.map(task => + task.id === taskId + ? { ...task, status: newStatus } + : task + )); + + toast({ + title: '状态更新成功', + description: `任务${newStatus === 1 ? '已启动' : '已暂停'}`, + }); + } catch (error: any) { + console.error('更新任务状态失败:', error); + toast({ + title: '更新失败', + description: error?.message || '请检查网络连接', + }); + } + }; + + return ( +
+ + + + } + /> + +
+ +
+
+ + setSearchName(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + /> +
+ + +
+
+ +
+ {tasks.map((task) => ( + +
+
+

{task.name}

+ + {task.status === 1 ? '进行中' : '已暂停'} + +
+
+ toggleTaskStatus(task.id, task.status)} /> + + + + + + handleView(task.id)}> + + 查看 + + handleEdit(task.id)}> + + 编辑 + + handleCopy(task.id)}> + + 复制 + + handleDelete(task.id)}> + + 删除 + + + +
+
+ +
+
+
执行设备:{task.config.devices.length} 个
+
目标人群:{task.config.friends?.length || 0} 个
+
更新时间:{task.updateTime}
+
+
+
点赞间隔:{task.config.interval} 秒
+
每日上限:{task.config.maxLikes} 次
+
创建时间:{task.createTime}
+
+
+ +
+
+
+ + 今日点赞: + {task.config.todayLikeCount || 0} 次 +
+
+
+
+ + 总点赞数: + {task.config.totalLikeCount || 0} 次 +
+
+
+ + {expandedTaskId === task.id && ( +
+
+
+
+ +

基本设置

+
+
+
+ 点赞间隔: + {task.config.interval} 秒 +
+
+ 每日最大点赞数: + {task.config.maxLikes} 次 +
+
+ 每个好友最大点赞数: + {task.config.friendMaxLikes || 3} 次 +
+
+ 执行时间段: + + {task.config.startTime} - {task.config.endTime} + +
+
+
+ +
+
+ +

目标人群

+
+
+
+ {task.config.targetGroups.map((tag, index) => ( + + {tag} + + ))} +
+
+ 匹配方式:{task.config.tagOperator === 1 ? '满足所有标签' : '满足任一标签'} +
+ {task.config.enableFriendTags && task.config.friendTags && ( +
+
好友标签:
+ + {task.config.friendTags} + +
+ )} +
+
+ +
+
+ +

点赞内容类型

+
+
+
+ {task.config.contentTypes.map((type, index) => ( + + {type === 'text' ? '文字' : type === 'image' ? '图片' : '视频'} + + ))} +
+
+
+
+
+ )} +
+ ))} +
+ + {/* 分页 */} + {total > pageSize && ( +
+ +
+ 第 {currentPage} 页 + 共 {Math.ceil(total / pageSize)} 页 +
+ +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx b/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx index ab0720f8..cd2b1540 100644 --- a/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx +++ b/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx @@ -1,5 +1,354 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import { Plus, Filter, Search, RefreshCw, MoreVertical, Clock, Edit, Trash2, Eye, Copy } from 'lucide-react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { Switch } from '@/components/ui/switch'; +import PageHeader from '@/components/PageHeader'; +import { useToast } from '@/components/ui/toast'; + +interface SyncTask { + id: string; + name: string; + status: number; // 1-运行中,0-暂停 + deviceCount: number; + contentLib: string; + syncCount: number; + lastSyncTime: string; + createTime: string; + creator: string; + config: { + devices: string[]; + contentLibraryNames: string[]; + }; + creatorName: string; +} export default function MomentsSync() { - return
朋友圈同步页
; + const navigate = useNavigate(); + const { toast } = useToast(); + const [tasks, setTasks] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [total, setTotal] = useState(0); + + // 模拟数据 + const mockTasks: SyncTask[] = [ + { + id: '1', + name: '朋友圈同步任务1', + status: 1, + deviceCount: 3, + contentLib: '营销素材库', + syncCount: 156, + lastSyncTime: '2024-03-18 16:30:00', + createTime: '2024-03-15 10:00:00', + creator: 'admin', + creatorName: '管理员', + config: { + devices: ['device1', 'device2', 'device3'], + contentLibraryNames: ['营销素材库', '产品介绍库'] + } + }, + { + id: '2', + name: '朋友圈同步任务2', + status: 0, + deviceCount: 2, + contentLib: '产品介绍库', + syncCount: 89, + lastSyncTime: '2024-03-17 14:20:00', + createTime: '2024-03-14 15:30:00', + creator: 'user1', + creatorName: '用户1', + config: { + devices: ['device4', 'device5'], + contentLibraryNames: ['产品介绍库'] + } + } + ]; + + // 获取任务列表 + const fetchTasks = async () => { + setIsLoading(true); + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 1000)); + + // 模拟搜索过滤 + let filteredTasks = mockTasks; + if (searchQuery) { + filteredTasks = mockTasks.filter(task => + task.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + + setTasks(filteredTasks); + setTotal(filteredTasks.length); + } catch (error: any) { + console.error('获取朋友圈同步任务列表失败:', error); + toast({ + title: '获取失败', + description: error?.message || '请检查网络连接', + }); + } finally { + setIsLoading(false); + } + }; + + // 组件加载时获取任务列表 + useEffect(() => { + fetchTasks(); + }, [currentPage, pageSize]); + + // 处理页码变化 + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + // 处理每页条数变化 + const handlePageSizeChange = (size: number) => { + setPageSize(size); + setCurrentPage(1); // 重置到第一页 + }; + + // 搜索任务 + const handleSearch = () => { + fetchTasks(); + }; + + // 切换任务状态 + const toggleTaskStatus = async (taskId: string, currentStatus: number) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + const newStatus = currentStatus === 1 ? 0 : 1; + setTasks( + tasks.map((task) => + task.id === taskId ? { ...task, status: newStatus } : task + ) + ); + + toast({ + title: '状态更新成功', + description: `任务已${newStatus === 1 ? '启用' : '暂停'}`, + }); + } catch (error: any) { + console.error('更新任务状态失败:', error); + toast({ + title: '更新失败', + description: error?.message || '更新任务状态失败', + }); + } + }; + + // 执行删除 + const handleDelete = async (taskId: string) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + setTasks(tasks.filter((task) => task.id !== taskId)); + toast({ + title: '删除成功', + description: '已成功删除同步任务', + }); + } catch (error: any) { + console.error('删除任务失败:', error); + toast({ + title: '删除失败', + description: error?.message || '删除任务失败', + }); + } + }; + + // 编辑任务 + const handleEdit = (taskId: string) => { + navigate(`/workspace/moments-sync/${taskId}/edit`); + }; + + // 查看任务详情 + const handleView = (taskId: string) => { + navigate(`/workspace/moments-sync/${taskId}`); + }; + + // 复制任务 + const handleCopy = async (taskId: string) => { + try { + // 模拟API调用 + await new Promise(resolve => setTimeout(resolve, 500)); + + const taskToCopy = tasks.find(task => task.id === taskId); + if (taskToCopy) { + const newTask = { + ...taskToCopy, + id: Date.now().toString(), + name: `${taskToCopy.name} (副本)`, + status: 0, + createTime: new Date().toLocaleString(), + syncCount: 0, + lastSyncTime: '-' + }; + setTasks([newTask, ...tasks]); + toast({ + title: '复制成功', + description: '已成功复制同步任务', + }); + } + } catch (error: any) { + console.error('复制任务失败:', error); + toast({ + title: '复制失败', + description: error?.message || '复制任务失败', + }); + } + }; + + // 过滤任务 + const filteredTasks = tasks.filter( + (task) => task.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return ( +
+ + + + } + /> + +
+ +
+
+ + setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + /> +
+ + +
+
+ +
+ {isLoading ? ( +
加载中...
+ ) : filteredTasks.length > 0 ? ( + filteredTasks.map((task) => ( + +
+
+

{task.name}

+ + {task.status === 1 ? '运行中' : '已暂停'} + +
+
+ toggleTaskStatus(task.id, task.status)} /> + + + + + + handleView(task.id)}> + + 查看 + + handleEdit(task.id)}> + + 编辑 + + handleCopy(task.id)}> + + 复制 + + handleDelete(task.id)}> + + 删除 + + + +
+
+ +
+
+
执行设备:{task.deviceCount} 个
+
内容库:{task.contentLib}
+
创建者:{task.creatorName}
+
+
+
同步次数:{task.syncCount} 次
+
最后同步:{task.lastSyncTime}
+
创建时间:{task.createTime}
+
+
+ +
+
+ + 配置的设备:{task.config.devices.join(', ')} +
+
+
+ )) + ) : ( +
+ {searchQuery ? '没有找到匹配的任务' : '暂无同步任务'} +
+ )} +
+ + {/* 分页 */} + {total > pageSize && ( +
+ +
+ 第 {currentPage} 页 + 共 {Math.ceil(total / pageSize)} 页 +
+ +
+ )} +
+
+ ); } \ No newline at end of file