feat: 功能迁移过来了,接下来优化样式

This commit is contained in:
许永平
2025-07-06 13:18:08 +08:00
parent ae6821d917
commit 2cdbd4dd73
14 changed files with 2288 additions and 127 deletions

View File

@@ -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<number | null>(null);
const [tasks, setTasks] = useState<Task[]>([]);
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 (
<div className="flex-1 bg-gray-50 min-h-screen pb-20">
<PageHeader
title="自动点赞"
defaultBackPath="/workspace"
rightContent={
<Link to="/workspace/auto-like/new">
<Button>
<Plus className="h-4 w-4 mr-2" />
</Button>
</Link>
}
/>
<div className="p-4">
<Card className="p-4 mb-4">
<div className="flex items-center space-x-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索任务名称"
className="pl-9"
value={searchName}
onChange={(e) => setSearchName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
/>
</div>
<Button variant="outline" size="icon" onClick={handleSearch}>
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" onClick={handleRefresh} disabled={loading}>
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
</Button>
</div>
</Card>
<div className="space-y-4">
{tasks.map((task) => (
<Card key={task.id} className="p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-2">
<h3 className="font-medium">{task.name}</h3>
<Badge variant={task.status === 1 ? 'default' : 'secondary'}>
{task.status === 1 ? '进行中' : '已暂停'}
</Badge>
</div>
<div className="flex items-center space-x-2">
<Switch checked={task.status === 1} onCheckedChange={() => toggleTaskStatus(task.id, task.status)} />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleView(task.id)}>
<Eye className="h-4 w-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleEdit(task.id)}>
<Edit className="h-4 w-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleCopy(task.id)}>
<Copy className="h-4 w-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDelete(task.id)}>
<Trash2 className="h-4 w-4 mr-2" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="text-sm text-gray-500">
<div className="mb-1">{task.config.devices.length} </div>
<div className="mb-1">{task.config.friends?.length || 0} </div>
<div>{task.updateTime}</div>
</div>
<div className="text-sm text-gray-500">
<div className="mb-1">{task.config.interval} </div>
<div className="mb-1">{task.config.maxLikes} </div>
<div>{task.createTime}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4 border-t pt-4">
<div className="text-sm">
<div className="flex items-center">
<ThumbsUp className="h-4 w-4 mr-2 text-blue-500" />
<span className="text-gray-500"></span>
<span className="ml-1 font-medium">{task.config.todayLikeCount || 0} </span>
</div>
</div>
<div className="text-sm">
<div className="flex items-center">
<ThumbsUp className="h-4 w-4 mr-2 text-green-500" />
<span className="text-gray-500"></span>
<span className="ml-1 font-medium">{task.config.totalLikeCount || 0} </span>
</div>
</div>
</div>
{expandedTaskId === task.id && (
<div className="mt-4 pt-4 border-t border-dashed">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div className="flex items-center">
<Settings className="h-5 w-5 mr-2 text-gray-500" />
<h4 className="font-medium"></h4>
</div>
<div className="space-y-2 pl-7">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{task.config.interval} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{task.config.maxLikes} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{task.config.friendMaxLikes || 3} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>
{task.config.startTime} - {task.config.endTime}
</span>
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center">
<Users className="h-5 w-5 mr-2 text-gray-500" />
<h4 className="font-medium"></h4>
</div>
<div className="space-y-2 pl-7">
<div className="flex flex-wrap gap-2">
{task.config.targetGroups.map((tag, index) => (
<Badge key={`${task.id}-tag-${index}-${tag}`} variant="outline" className="bg-gray-50">
{tag}
</Badge>
))}
</div>
<div className="text-sm text-gray-500">
{task.config.tagOperator === 1 ? '满足所有标签' : '满足任一标签'}
</div>
{task.config.enableFriendTags && task.config.friendTags && (
<div className="mt-2">
<div className="text-sm font-medium mb-1"></div>
<Badge variant="outline" className="bg-blue-50 border-blue-200">
{task.config.friendTags}
</Badge>
</div>
)}
</div>
</div>
<div className="space-y-4">
<div className="flex items-center">
<ThumbsUp className="h-5 w-5 mr-2 text-gray-500" />
<h4 className="font-medium"></h4>
</div>
<div className="space-y-2 pl-7">
<div className="flex flex-wrap gap-2">
{task.config.contentTypes.map((type, index) => (
<Badge key={`${task.id}-type-${index}-${type}`} variant="outline" className="bg-gray-50">
{type === 'text' ? '文字' : type === 'image' ? '图片' : '视频'}
</Badge>
))}
</div>
</div>
</div>
</div>
</div>
)}
</Card>
))}
</div>
{/* 分页 */}
{total > pageSize && (
<div className="flex justify-center mt-6 space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1 || loading}
>
</Button>
<div className="flex items-center space-x-1">
<span className="text-sm text-gray-500"> {currentPage} </span>
<span className="text-sm text-gray-500"> {Math.ceil(total / pageSize)} </span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(Math.ceil(total / pageSize), prev + 1))}
disabled={currentPage >= Math.ceil(total / pageSize) || loading}
>
</Button>
</div>
)}
</div>
</div>
);
}