feat: 本次提交更新内容如下

样式暂时可以了
This commit is contained in:
2025-07-08 11:53:38 +08:00
parent 6129fbfac6
commit f0d10e12ab
9 changed files with 13093 additions and 12305 deletions

View File

@@ -15,6 +15,7 @@ import WechatAccountDetail from './pages/wechat-accounts/WechatAccountDetail';
import Workspace from './pages/workspace/Workspace';
import AutoLike from './pages/workspace/auto-like/AutoLike';
import NewAutoLike from './pages/workspace/auto-like/NewAutoLike';
import AutoLikeDetail from './pages/workspace/auto-like/AutoLikeDetail';
import AutoGroup from './pages/workspace/auto-group/AutoGroup';
import AutoGroupDetail from './pages/workspace/auto-group/Detail';
import GroupPush from './pages/workspace/group-push/GroupPush';
@@ -58,6 +59,7 @@ function App() {
<Route path="/workspace" element={<Workspace />} />
<Route path="/workspace/auto-like" element={<AutoLike />} />
<Route path="/workspace/auto-like/new" element={<NewAutoLike />} />
<Route path="/workspace/auto-like/:id" element={<AutoLikeDetail />} />
<Route path="/workspace/auto-group" element={<AutoGroup />} />
<Route path="/workspace/auto-group/:id" element={<AutoGroupDetail />} />
<Route path="/workspace/group-push" element={<GroupPush />} />

View File

@@ -1,35 +1,90 @@
import { get, post } from './request';
export interface LikeTask {
id: string;
name: string;
status: 'running' | 'paused';
deviceCount: number;
targetGroup: string;
likeCount: number;
lastLikeTime: string;
createTime: string;
creator: string;
likeInterval: number;
maxLikesPerDay: number;
timeRange: { start: string; end: string };
contentTypes: string[];
targetTags: string[];
}
import { get, post, del } from './request';
import {
LikeTask,
CreateLikeTaskData,
UpdateLikeTaskData,
LikeRecord,
ApiResponse,
PaginatedResponse
} from '@/types/auto-like';
// 获取自动点赞任务列表
export async function fetchAutoLikeTasks(): Promise<LikeTask[]> {
const res = await get('/api/workbench/auto-like/list');
return res.data?.list || [];
try {
const res = await get<ApiResponse<PaginatedResponse<LikeTask>>>('/v1/workbench/list?type=1&page=1&limit=100');
if (res.code === 200 && res.data) {
return res.data.list || [];
}
return [];
} catch (error) {
console.error('获取自动点赞任务失败:', error);
return [];
}
}
export async function deleteAutoLikeTask(id: string) {
return post('/api/workbench/auto-like/delete', { id });
// 获取单个任务详情
export async function fetchAutoLikeTaskDetail(id: string): Promise<LikeTask | null> {
try {
const res = await get<ApiResponse<LikeTask>>(`/v1/workbench/detail/${id}`);
if (res.code === 200 && res.data) {
return res.data;
}
return null;
} catch (error) {
console.error('获取任务详情失败:', error);
return null;
}
}
export async function toggleAutoLikeTask(id: string, status: string) {
return post('/api/workbench/auto-like/toggle', { id, status });
// 创建自动点赞任务
export async function createAutoLikeTask(data: CreateLikeTaskData): Promise<ApiResponse> {
return post('/v1/workbench/create', {
...data,
type: 1 // 自动点赞类型
});
}
export async function copyAutoLikeTask(id: string) {
return post('/api/workbench/auto-like/copy', { id });
}
// 更新自动点赞任务
export async function updateAutoLikeTask(data: UpdateLikeTaskData): Promise<ApiResponse> {
return post('/v1/workbench/update', {
...data,
type: 1 // 自动点赞类型
});
}
// 删除自动点赞任务
export async function deleteAutoLikeTask(id: string): Promise<ApiResponse> {
return del('/v1/workbench/delete', { params: { id } });
}
// 切换任务状态
export async function toggleAutoLikeTask(id: string, status: string): Promise<ApiResponse> {
return post('/v1/workbench/update-status', { id, status });
}
// 复制自动点赞任务
export async function copyAutoLikeTask(id: string): Promise<ApiResponse> {
return post('/v1/workbench/copy', { id });
}
// 获取点赞记录
export async function fetchLikeRecords(
workbenchId: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<LikeRecord>> {
try {
const res = await get<ApiResponse<PaginatedResponse<LikeRecord>>>(`/v1/workbench/like-records?workbenchId=${workbenchId}&page=${page}&limit=${limit}`);
if (res.code === 200 && res.data) {
return res.data;
}
return { list: [], total: 0, page, limit };
} catch (error) {
console.error('获取点赞记录失败:', error);
return { list: [], total: 0, page, limit };
}
}
export type { LikeTask, LikeRecord, CreateLikeTaskData };

View File

@@ -1,69 +1,108 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import React, { useState, useCallback } from "react";
import {
Plus,
Search,
RefreshCw,
MoreVertical,
Clock,
Edit,
Trash2,
Eye,
Copy,
ChevronDown,
ChevronUp,
MoreVertical,
Eye,
Edit,
Copy,
Trash2,
Clock,
Plus,
Filter,
Search,
RefreshCw,
Settings,
Calendar,
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 { Switch } from '@/components/ui/switch';
import { Progress } from '@/components/ui/progress';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import Layout from '@/components/Layout';
import PageHeader from '@/components/PageHeader';
import BottomNav from '@/components/BottomNav';
import { useToast } from '@/components/ui/toast';
import '@/components/Layout.css';
import {
fetchAutoLikeTasks,
deleteAutoLikeTask,
toggleAutoLikeTask,
copyAutoLikeTask,
LikeTask,
} from '@/api/autoLike';
} 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 { useNavigate } from "react-router-dom";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/toast";
import { Progress } from "@/components/ui/progress";
import { fetchAutoLikeTasks, deleteAutoLikeTask, toggleAutoLikeTask, copyAutoLikeTask, LikeTask } from '@/api/autoLike';
type CardMenuProps = {
onView: () => void;
onEdit: () => void;
onCopy: () => void;
onDelete: () => void;
};
function CardMenu({ onView, onEdit, onCopy, onDelete }: CardMenuProps) {
const [open, setOpen] = React.useState(false);
const menuRef = React.useRef<HTMLDivElement | null>(null);
React.useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setOpen(false);
}
}
if (open) document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [open]);
return (
<div style={{ position: "relative" }}>
<button onClick={() => setOpen((v) => !v)} style={{ background: "none", border: "none", padding: 0, margin: 0, cursor: "pointer" }}>
<MoreVertical className="h-4 w-4" />
</button>
{open && (
<div
ref={menuRef}
style={{
position: "absolute",
right: 0,
top: 28,
background: "#fff",
borderRadius: 8,
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
zIndex: 100,
minWidth: 120,
padding: 4,
}}
>
<div onClick={() => { onView(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}>
<Eye className="h-4 w-4 mr-2" />
</div>
<div onClick={() => { onEdit(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}>
<Edit className="h-4 w-4 mr-2" />
</div>
<div onClick={() => { onCopy(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}>
<Copy className="h-4 w-4 mr-2" />
</div>
<div onClick={() => { onDelete(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, color: "#e53e3e", transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}>
<Trash2 className="h-4 w-4 mr-2" />
</div>
</div>
)}
</div>
);
}
export default function AutoLike() {
const navigate = useNavigate();
const { toast } = useToast();
const [expandedTaskId, setExpandedTaskId] = useState<string | null>(null);
const [searchTerm, setSearchTerm] = useState('');
const [tasks, setTasks] = useState<LikeTask[]>([]);
const [loading, setLoading] = useState(false);
const [tasks, setTasks] = React.useState<LikeTask[]>([]);
const [searchTerm, setSearchTerm] = useState("");
// 获取任务列表
const fetchTasks = useCallback(async () => {
setLoading(true);
try {
const list = await fetchAutoLikeTasks();
setTasks(list);
} catch {
toast({ title: '获取任务失败', variant: 'destructive' });
} finally {
setLoading(false);
} catch (error) {
toast({ title: "获取任务失败", variant: "destructive" });
}
}, [toast]);
useEffect(() => {
React.useEffect(() => {
fetchTasks();
}, [fetchTasks]);
@@ -72,13 +111,17 @@ export default function AutoLike() {
};
const handleDelete = async (id: string) => {
if (!window.confirm('确定要删除该任务吗?')) return;
if (!window.confirm("确定要删除该任务吗?")) return;
try {
await deleteAutoLikeTask(id);
toast({ title: '删除成功' });
fetchTasks();
} catch {
toast({ title: '删除失败', variant: 'destructive' });
const response = await deleteAutoLikeTask(id);
if (response.code === 200) {
toast({ title: "删除成功" });
fetchTasks();
} else {
toast({ title: "删除失败", description: response.msg || "请稍后重试", variant: "destructive" });
}
} catch (error) {
toast({ title: "删除失败", description: "请稍后重试", variant: "destructive" });
}
};
@@ -92,188 +135,110 @@ export default function AutoLike() {
const handleCopy = async (id: string) => {
try {
await copyAutoLikeTask(id);
toast({ title: '复制成功' });
fetchTasks();
} catch {
toast({ title: '复制失败', variant: 'destructive' });
const response = await copyAutoLikeTask(id);
if (response.code === 200) {
toast({ title: "复制成功" });
fetchTasks();
} else {
toast({ title: "复制失败", description: response.msg || "请稍后重试", variant: "destructive" });
}
} catch (error) {
toast({ title: "复制失败", description: "请稍后重试", variant: "destructive" });
}
};
const toggleTaskStatus = async (id: string, status: string) => {
try {
await toggleAutoLikeTask(id, status);
toast({ title: '操作成功' });
fetchTasks();
} catch {
toast({ title: '操作失败', variant: 'destructive' });
// status: 'running' -> 2要关闭'paused' -> 1要开启
const newStatus = status === "running" ? 2 : 1;
const response = await toggleAutoLikeTask(id, String(newStatus));
if (response.code === 200) {
toast({ title: "操作成功" });
fetchTasks();
} else {
toast({ title: "操作失败", description: response.msg || "请稍后重试", variant: "destructive" });
}
} catch (error) {
toast({ title: "操作失败", description: "请稍后重试", variant: "destructive" });
}
};
const handleCreateNew = () => {
navigate('/workspace/auto-like/new');
navigate("/workspace/auto-like/new");
};
const filteredTasks = tasks.filter((task) =>
task.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
const getStatusColor = (status: string) => {
switch (status) {
case 'running':
return 'bg-green-100 text-green-800';
case 'paused':
return 'bg-gray-100 text-gray-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'running':
return '进行中';
case 'paused':
return '已暂停';
default:
return '未知';
}
};
return (
<Layout
header={
<PageHeader
title="自动点赞"
defaultBackPath="/workspace"
rightContent={
<Button onClick={handleCreateNew}>
<Plus className="h-4 w-4 mr-2" />
<div className="flex-1 bg-gray-50 min-h-screen pb-20">
<header className="sticky top-0 z-10 bg-white border-b">
<div className="flex items-center justify-between p-4">
<div className="flex items-center space-x-3">
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
<ChevronDown className="h-5 w-5" />
</Button>
}
/>
}
footer={<BottomNav />}
>
<div className="bg-gray-50 min-h-screen pb-20">
<h1 className="text-lg font-medium"></h1>
</div>
<Button onClick={handleCreateNew}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
</header>
<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={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Input placeholder="搜索任务名称" className="pl-9" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
</div>
{/* 移除筛选按钮 */}
{/* <Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button> */}
<Button variant="outline" size="icon" onClick={fetchTasks}>
<RefreshCw className="h-4 w-4" />
</Button>
</div>
</Card>
{/* 任务列表 */}
<div className="space-y-4">
{loading ? (
<Card className="p-8 text-center">
<ThumbsUp className="h-12 w-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500 text-lg font-medium mb-2">...</p>
<p className="text-gray-400 text-sm mb-4"></p>
</Card>
) : filteredTasks.length === 0 ? (
<Card className="p-8 text-center">
<ThumbsUp className="h-12 w-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500 text-lg font-medium mb-2"></p>
<p className="text-gray-400 text-sm mb-4"></p>
<Button onClick={handleCreateNew}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</Card>
) : (
filteredTasks.map((task) => (
{filteredTasks.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 className={getStatusColor(task.status)}>
{getStatusText(task.status)}
<Badge variant={task.status === "running" ? "success" : "secondary"}>
{task.status === "running" ? "进行中" : "已暂停"}
</Badge>
</div>
<div className="flex items-center space-x-2">
<Switch
checked={task.status === 'running'}
onCheckedChange={() => toggleTaskStatus(task.id, task.status === 'running' ? 'paused' : 'running')}
/>
<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>
<Switch checked={task.status === "running"} onCheckedChange={() => toggleTaskStatus(task.id, task.status)} />
<CardMenu
onView={() => handleView(task.id)}
onEdit={() => handleEdit(task.id)}
onCopy={() => handleCopy(task.id)}
onDelete={() => handleDelete(task.id)}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="text-sm text-gray-500">
<div>{task.deviceCount} </div>
<div>{task.targetGroup}</div>
<div>{task.deviceCount} </div>
<div>{task.targetGroup}</div>
</div>
<div className="text-sm text-gray-500">
<div>{task.likeCount} </div>
<div>{task.creator}</div>
<div>{task.likeCount} </div>
<div>{task.creator}</div>
</div>
</div>
<div className="flex items-center justify-between text-xs text-gray-500 border-t pt-4">
<div className="flex items-center">
<Clock className="w-4 h-4 mr-1" />
{task.lastLikeTime}
</div>
<div className="flex items-center">
<span>{task.createTime}</span>
<Button
variant="ghost"
size="sm"
className="ml-2 p-0 h-6 w-6"
onClick={() => toggleExpand(task.id)}
>
{expandedTaskId === task.id ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
<div className="flex items-center justify-between text-xs text-gray-500 border-t pt-4">
<div className="flex items-center">
<Clock className="w-4 h-4 mr-1" />{task.lastLikeTime}
</div>
<div className="flex items-center">
<span>{task.createTime}</span>
<Button variant="ghost" size="sm" className="ml-2 p-0 h-6 w-6" onClick={() => toggleExpand(task.id)}>
{expandedTaskId === task.id ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>
</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">
@@ -285,21 +250,20 @@ export default function AutoLike() {
<div className="space-y-2 pl-7">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{task.likeInterval} </span>
<span>{task.likeInterval} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>{task.maxLikesPerDay} </span>
<span>{task.maxLikesPerDay} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span>
{task.timeRange.start} - {task.timeRange.end}
{task.timeRange.start} - {task.timeRange.end}
</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" />
@@ -307,15 +271,14 @@ export default function AutoLike() {
</div>
<div className="space-y-2 pl-7">
<div className="flex flex-wrap gap-2">
{task.targetTags.map((tag) => (
<Badge key={tag} variant="outline" className="bg-gray-50">
{task.targetTags.map((tag) => (
<Badge key={tag} variant="outline" className="bg-gray-50">
{tag}
</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" />
@@ -323,42 +286,39 @@ export default function AutoLike() {
</div>
<div className="space-y-2 pl-7">
<div className="flex flex-wrap gap-2">
{task.contentTypes.map((type) => (
<Badge key={type} variant="outline" className="bg-gray-50">
{type === 'text' ? '文字' : type === 'image' ? '图片' : '视频'}
{task.contentTypes.map((type) => (
<Badge key={type} variant="outline" className="bg-gray-50">
{type === "text" ? "文字" : type === "image" ? "图片" : "视频"}
</Badge>
))}
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center">
<Calendar 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 mb-1">
<span className="text-gray-500"></span>
<span>
{task.likeCount} / {task.maxLikesPerDay}
</span>
</div>
<Progress
value={(task.likeCount / task.maxLikesPerDay) * 100}
className="h-2"
/>
<div className="space-y-4">
<div className="flex items-center">
<Calendar 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 mb-1">
<span className="text-gray-500"></span>
<span>
{task.likeCount} / {task.maxLikesPerDay}
</span>
</div>
<Progress
value={(task.likeCount / task.maxLikesPerDay) * 100}
className="h-2"
/>
</div>
</div>
</div>
</div>
)}
</Card>
))
)}
</div>
))}
</div>
</div>
</Layout>
</div>
);
}

View File

@@ -0,0 +1,452 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import {
Edit,
Trash2,
Copy,
ThumbsUp,
Settings,
Calendar,
Eye,
RefreshCw,
} from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Switch } from '@/components/ui/switch';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import Layout from '@/components/Layout';
import PageHeader from '@/components/PageHeader';
import BottomNav from '@/components/BottomNav';
import { useToast } from '@/components/ui/toast';
import '@/components/Layout.css';
import {
fetchAutoLikeTaskDetail,
toggleAutoLikeTask,
deleteAutoLikeTask,
copyAutoLikeTask,
fetchLikeRecords,
LikeTask,
LikeRecord,
} from '@/api/autoLike';
export default function AutoLikeDetail() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { toast } = useToast();
const [task, setTask] = useState<LikeTask | null>(null);
const [records, setRecords] = useState<LikeRecord[]>([]);
const [loading, setLoading] = useState(true);
const [recordsLoading, setRecordsLoading] = useState(false);
const fetchTaskDetail = useCallback(async () => {
if (!id) return;
try {
const taskData = await fetchAutoLikeTaskDetail(id);
if (taskData) {
setTask(taskData);
} else {
toast({
title: '任务不存在',
description: '该任务可能已被删除',
variant: 'destructive',
});
navigate('/workspace/auto-like');
}
} catch (error) {
console.error('获取任务详情失败:', error);
toast({
title: '获取失败',
description: '请稍后重试',
variant: 'destructive',
});
} finally {
setLoading(false);
}
}, [id, toast, navigate]);
const fetchRecords = useCallback(async () => {
if (!id) return;
setRecordsLoading(true);
try {
const response = await fetchLikeRecords(id, 1, 20);
setRecords(response.list || []);
} catch (error) {
console.error('获取点赞记录失败:', error);
} finally {
setRecordsLoading(false);
}
}, [id]);
useEffect(() => {
if (id) {
fetchTaskDetail();
fetchRecords();
}
}, [id, fetchTaskDetail, fetchRecords]);
const handleToggleStatus = async () => {
if (!task) return;
try {
const newStatus = task.status === 'running' ? 'paused' : 'running';
const response = await toggleAutoLikeTask(task.id, newStatus);
if (response.code === 200) {
setTask(prev => prev ? { ...prev, status: newStatus } : null);
toast({ title: '操作成功' });
} else {
toast({
title: '操作失败',
description: response.msg || '请稍后重试',
variant: 'destructive',
});
}
} catch (error) {
console.error('操作失败:', error);
toast({
title: '操作失败',
description: '请稍后重试',
variant: 'destructive',
});
}
};
const handleDelete = async () => {
if (!task || !window.confirm('确定要删除该任务吗?')) return;
try {
const response = await deleteAutoLikeTask(task.id);
if (response.code === 200) {
toast({ title: '删除成功' });
navigate('/workspace/auto-like');
} else {
toast({
title: '删除失败',
description: response.msg || '请稍后重试',
variant: 'destructive',
});
}
} catch (error) {
console.error('删除失败:', error);
toast({
title: '删除失败',
description: '请稍后重试',
variant: 'destructive',
});
}
};
const handleCopy = async () => {
if (!task) return;
try {
const response = await copyAutoLikeTask(task.id);
if (response.code === 200) {
toast({ title: '复制成功' });
navigate('/workspace/auto-like');
} else {
toast({
title: '复制失败',
description: response.msg || '请稍后重试',
variant: 'destructive',
});
}
} catch (error) {
console.error('复制失败:', error);
toast({
title: '复制失败',
description: '请稍后重试',
variant: 'destructive',
});
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'running':
return 'bg-green-100 text-green-800';
case 'paused':
return 'bg-gray-100 text-gray-800';
case 'completed':
return 'bg-blue-100 text-blue-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'running':
return '进行中';
case 'paused':
return '已暂停';
case 'completed':
return '已完成';
default:
return '未知';
}
};
if (loading) {
return (
<Layout
header={<PageHeader title="任务详情" defaultBackPath="/workspace/auto-like" />}
footer={<BottomNav />}
>
<div className="bg-gray-50 min-h-screen pb-20">
<div className="p-4">
<Card className="p-8 text-center">
<RefreshCw className="h-12 w-12 text-gray-300 mx-auto mb-3 animate-spin" />
<p className="text-gray-500 text-lg font-medium mb-2">...</p>
<p className="text-gray-400 text-sm"></p>
</Card>
</div>
</div>
</Layout>
);
}
if (!task) {
return null;
}
return (
<Layout
header={
<PageHeader
title="任务详情"
defaultBackPath="/workspace/auto-like"
rightContent={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<Settings className="h-4 w-4 mr-2" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => navigate(`/workspace/auto-like/${task.id}/edit`)}>
<Edit className="h-4 w-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={handleCopy}>
<Copy className="h-4 w-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={handleDelete}>
<Trash2 className="h-4 w-4 mr-2" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
}
/>
}
footer={<BottomNav />}
>
<div className="bg-gray-50 min-h-screen pb-20">
<div className="p-4 space-y-4">
{/* 任务基本信息 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center">
<ThumbsUp className="h-5 w-5 mr-2" />
{task.name}
</CardTitle>
<div className="flex items-center space-x-2">
<Badge className={getStatusColor(task.status)}>
{getStatusText(task.status)}
</Badge>
<Switch
checked={task.status === 'running'}
onCheckedChange={handleToggleStatus}
disabled={task.status === 'completed'}
/>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.createTime}</div>
</div>
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.creator}</div>
</div>
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.deviceCount} </div>
</div>
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.targetGroup}</div>
</div>
</div>
</CardContent>
</Card>
{/* 执行进度 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Calendar className="h-5 w-5 mr-2" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-500"></span>
<span className="font-medium">
{task.todayLikeCount} / {task.maxLikesPerDay}
</span>
</div>
<Progress
value={(task.todayLikeCount / task.maxLikesPerDay) * 100}
className="h-2"
/>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<div className="text-gray-500"></div>
<div className="font-medium">{task.totalLikeCount} </div>
</div>
<div>
<div className="text-gray-500"></div>
<div className="font-medium">{task.lastLikeTime}</div>
</div>
</div>
</CardContent>
</Card>
{/* 任务配置 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Settings className="h-5 w-5 mr-2" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.likeInterval} </div>
</div>
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.maxLikesPerDay} </div>
</div>
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">{task.friendMaxLikes} </div>
</div>
<div className="text-sm">
<div className="text-gray-500"></div>
<div className="font-medium">
{task.timeRange.start} - {task.timeRange.end}
</div>
</div>
</div>
<div className="space-y-2">
<div className="text-sm text-gray-500"></div>
<div className="flex flex-wrap gap-2">
{task.contentTypes.map((type) => (
<Badge key={type} variant="outline">
{type === 'text' ? '文字' : type === 'image' ? '图片' : type === 'video' ? '视频' : '链接'}
</Badge>
))}
</div>
</div>
{task.targetTags.length > 0 && (
<div className="space-y-2">
<div className="text-sm text-gray-500"></div>
<div className="flex flex-wrap gap-2">
{task.targetTags.map((tag) => (
<Badge key={tag} variant="outline">
{tag}
</Badge>
))}
</div>
</div>
)}
{task.enableFriendTags && task.friendTags && (
<div className="space-y-2">
<div className="text-sm text-gray-500"></div>
<div className="flex flex-wrap gap-2">
<Badge variant="outline">{task.friendTags}</Badge>
</div>
</div>
)}
</CardContent>
</Card>
{/* 点赞记录 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center">
<Eye className="h-5 w-5 mr-2" />
</CardTitle>
<Button variant="outline" size="sm" onClick={fetchRecords}>
<RefreshCw className="h-4 w-4 mr-2" />
</Button>
</div>
</CardHeader>
<CardContent>
{recordsLoading ? (
<div className="text-center py-4">
<RefreshCw className="h-6 w-6 text-gray-400 mx-auto animate-spin" />
<p className="text-gray-500 text-sm mt-2">...</p>
</div>
) : records.length === 0 ? (
<div className="text-center py-8">
<ThumbsUp className="h-12 w-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500"></p>
</div>
) : (
<div className="space-y-3">
{records.slice(0, 10).map((record) => (
<div key={record.id} className="flex items-center space-x-3 p-3 border rounded-lg">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
<ThumbsUp className="h-4 w-4 text-blue-600" />
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">
{record.friendName}
</div>
<div className="text-xs text-gray-500 truncate">
{record.content}
</div>
</div>
<div className="text-xs text-gray-500">
{record.likeTime}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
</Layout>
);
}

View File

@@ -14,6 +14,8 @@ import PageHeader from '@/components/PageHeader';
import BottomNav from '@/components/BottomNav';
import { useToast } from '@/components/ui/toast';
import '@/components/Layout.css';
import { createAutoLikeTask, CreateLikeTaskData } from '@/api/autoLike';
import { ContentType } from '@/types/auto-like';
interface Device {
id: string;
@@ -33,20 +35,17 @@ export default function NewAutoLike() {
const navigate = useNavigate();
const { toast } = useToast();
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({
const [formData, setFormData] = useState<CreateLikeTaskData>({
name: '',
description: '',
likeInterval: 30,
maxLikesPerDay: 100,
interval: 30,
maxLikes: 100,
friendMaxLikes: 10,
startTime: '09:00',
endTime: '18:00',
contentTypes: ['text', 'image'],
includeKeywords: '',
excludeKeywords: '',
selectedDevices: [] as string[],
selectedGroups: [] as string[],
targetTags: [] as string[],
devices: [],
friends: [],
targetTags: [],
enableFriendTags: false,
friendTags: '',
});
@@ -70,48 +69,48 @@ export default function NewAutoLike() {
'VIP', '高价值', '活跃', '互动', '潜在', '新客户', '男性', '女性', '青年', '中年', '高收入', '中收入'
];
const handleInputChange = (field: string, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
const handleInputChange = (field: keyof CreateLikeTaskData, value: any) => {
setFormData((prev: CreateLikeTaskData) => ({ ...prev, [field]: value }));
};
const handleDeviceToggle = (deviceId: string) => {
setFormData(prev => ({
setFormData((prev: CreateLikeTaskData) => ({
...prev,
selectedDevices: prev.selectedDevices.includes(deviceId)
? prev.selectedDevices.filter(id => id !== deviceId)
: [...prev.selectedDevices, deviceId]
devices: prev.devices.includes(deviceId)
? prev.devices.filter((id: string) => id !== deviceId)
: [...prev.devices, deviceId]
}));
};
const handleGroupToggle = (groupId: string) => {
setFormData(prev => ({
setFormData((prev: CreateLikeTaskData) => ({
...prev,
selectedGroups: prev.selectedGroups.includes(groupId)
? prev.selectedGroups.filter(id => id !== groupId)
: [...prev.selectedGroups, groupId]
friends: prev.friends?.includes(groupId)
? prev.friends.filter((id: string) => id !== groupId)
: [...(prev.friends || []), groupId]
}));
};
const handleTagToggle = (tag: string) => {
setFormData(prev => ({
setFormData((prev: CreateLikeTaskData) => ({
...prev,
targetTags: prev.targetTags.includes(tag)
? prev.targetTags.filter(t => t !== tag)
? prev.targetTags.filter((t: string) => t !== tag)
: [...prev.targetTags, tag]
}));
};
const handleContentTypeToggle = (type: string) => {
setFormData(prev => ({
const handleContentTypeToggle = (type: ContentType) => {
setFormData((prev: CreateLikeTaskData) => ({
...prev,
contentTypes: prev.contentTypes.includes(type)
? prev.contentTypes.filter(t => t !== type)
? prev.contentTypes.filter((t: ContentType) => t !== type)
: [...prev.contentTypes, type]
}));
};
const handleNext = () => {
if (currentStep === 1 && (!formData.name || formData.selectedDevices.length === 0)) {
if (currentStep === 1 && (!formData.name || formData.devices.length === 0)) {
toast({
title: '请完善信息',
description: '请填写任务名称并选择至少一个设备',
@@ -119,7 +118,7 @@ export default function NewAutoLike() {
});
return;
}
if (currentStep === 2 && formData.selectedGroups.length === 0) {
if (currentStep === 2 && (!formData.friends || formData.friends.length === 0)) {
toast({
title: '请选择目标人群',
description: '请至少选择一个目标人群',
@@ -136,16 +135,23 @@ export default function NewAutoLike() {
const handleSubmit = async () => {
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await createAutoLikeTask(formData);
toast({
title: '创建成功',
description: '自动点赞任务已创建',
});
navigate('/workspace/auto-like');
if (response.code === 200) {
toast({
title: '创建成功',
description: '自动点赞任务已创建',
});
navigate('/workspace/auto-like');
} else {
toast({
title: '创建失败',
description: response.msg || '请稍后重试',
variant: 'destructive',
});
}
} catch (error) {
console.error('创建失败:', error);
toast({
title: '创建失败',
description: '请检查网络连接后重试',
@@ -200,30 +206,21 @@ export default function NewAutoLike() {
</CardContent>
</Card>
{/* 步骤1: 基本设置 */}
{/* 步骤内容 */}
{currentStep === 1 && (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label htmlFor="name"></Label>
<Input
id="name"
placeholder="请输入任务名称"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
/>
</div>
<div>
<Label htmlFor="description"></Label>
<Input
id="description"
placeholder="请输入任务描述(可选)"
value={formData.description}
onChange={(e) => handleInputChange('description', e.target.value)}
placeholder="请输入任务名称"
/>
</div>
</CardContent>
@@ -231,36 +228,33 @@ export default function NewAutoLike() {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-3">
{devices.map((device) => (
<div
key={device.id}
className={`flex items-center justify-between p-3 rounded-lg border cursor-pointer ${
formData.selectedDevices.includes(device.id)
className={`flex items-center p-3 border rounded-lg cursor-pointer transition-colors ${
formData.devices.includes(device.id)
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
onClick={() => handleDeviceToggle(device.id)}
>
<div className="flex items-center space-x-3">
<div className={`w-2 h-2 rounded-full ${
device.status === 'online' ? 'bg-green-500' : 'bg-gray-400'
}`} />
<div>
<div className="font-medium">{device.name}</div>
<div className="text-sm text-gray-500">
{device.status === 'online' ? '在线' : '离线'} ·
: {device.lastActive}
</div>
</div>
</div>
<Checkbox
checked={formData.selectedDevices.includes(device.id)}
checked={formData.devices.includes(device.id)}
onChange={() => handleDeviceToggle(device.id)}
/>
<div className="ml-3 flex-1">
<div className="font-medium">{device.name}</div>
<div className="text-sm text-gray-500">
: {device.status === 'online' ? '在线' : '离线'}
</div>
</div>
<div className={`w-2 h-2 rounded-full ${
device.status === 'online' ? 'bg-green-500' : 'bg-gray-400'
}`} />
</div>
))}
</div>
@@ -269,42 +263,34 @@ export default function NewAutoLike() {
</div>
)}
{/* 步骤2: 目标人群 */}
{currentStep === 2 && (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-3">
{targetGroups.map((group) => (
<div
key={group.id}
className={`flex items-center justify-between p-3 rounded-lg border cursor-pointer ${
formData.selectedGroups.includes(group.id)
className={`flex items-center p-3 border rounded-lg cursor-pointer transition-colors ${
formData.friends?.includes(group.id)
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
onClick={() => handleGroupToggle(group.id)}
>
<div>
<div className="font-medium">{group.name}</div>
<div className="text-sm text-gray-500">
{group.count}
</div>
<div className="flex flex-wrap gap-1 mt-1">
{group.tags.map((tag) => (
<Badge key={tag} variant="outline" className="text-xs">
{tag}
</Badge>
))}
</div>
</div>
<Checkbox
checked={formData.selectedGroups.includes(group.id)}
checked={formData.friends?.includes(group.id) || false}
onChange={() => handleGroupToggle(group.id)}
/>
<div className="ml-3 flex-1">
<div className="font-medium">{group.name}</div>
<div className="text-sm text-gray-500">
: {group.count} | : {group.tags.join(', ')}
</div>
</div>
</div>
))}
</div>
@@ -313,14 +299,14 @@ export default function NewAutoLike() {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{availableTags.map((tag) => (
<Badge
key={tag}
variant={formData.targetTags.includes(tag) ? 'default' : 'outline'}
variant={formData.targetTags.includes(tag) ? "default" : "outline"}
className="cursor-pointer"
onClick={() => handleTagToggle(tag)}
>
@@ -333,7 +319,6 @@ export default function NewAutoLike() {
</div>
)}
{/* 步骤3: 高级设置 */}
{currentStep === 3 && (
<div className="space-y-4">
<Card>
@@ -344,38 +329,45 @@ export default function NewAutoLike() {
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="interval"></Label>
<Input
<input
id="interval"
value={formData.likeInterval.toString()}
onChange={(e) => handleInputChange('likeInterval', parseInt(e.target.value))}
type="number"
className="w-full border rounded px-3 py-2 text-sm"
value={formData.interval}
onChange={(e) => handleInputChange('interval', parseInt(e.target.value))}
/>
</div>
<div>
<Label htmlFor="maxLikes"></Label>
<Input
<input
id="maxLikes"
value={formData.maxLikesPerDay.toString()}
onChange={(e) => handleInputChange('maxLikesPerDay', parseInt(e.target.value))}
type="number"
className="w-full border rounded px-3 py-2 text-sm"
value={formData.maxLikes}
onChange={(e) => handleInputChange('maxLikes', parseInt(e.target.value))}
/>
</div>
<div>
<Label htmlFor="friendMaxLikes"></Label>
<Input
<input
id="friendMaxLikes"
value={formData.friendMaxLikes.toString()}
type="number"
className="w-full border rounded px-3 py-2 text-sm"
value={formData.friendMaxLikes}
onChange={(e) => handleInputChange('friendMaxLikes', parseInt(e.target.value))}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="startTime"></Label>
<input
id="startTime"
type="time"
className="w-full border rounded px-3 py-2 text-sm"
value={formData.startTime}
onChange={(e) => handleInputChange('startTime', e.target.value)}
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"
/>
</div>
<div>
@@ -383,138 +375,54 @@ export default function NewAutoLike() {
<input
id="endTime"
type="time"
className="w-full border rounded px-3 py-2 text-sm"
value={formData.endTime}
onChange={(e) => handleInputChange('endTime', e.target.value)}
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"
/>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2 mb-4">
{[
{ value: 'text', label: '文字' },
{ value: 'image', label: '图片' },
{ value: 'video', label: '视频' },
{ value: 'link', label: '链接' },
].map((type) => (
<Badge
key={type.value}
variant={formData.contentTypes.includes(type.value) ? 'default' : 'outline'}
className="cursor-pointer"
onClick={() => handleContentTypeToggle(type.value)}
>
{type.label}
</Badge>
))}
</div>
<div className="mb-2">
<Label htmlFor="includeKeywords"></Label>
<textarea
id="includeKeywords"
className="w-full border rounded p-2 text-sm"
placeholder="多个关键词用逗号或换行分隔"
value={formData.includeKeywords}
onChange={e => handleInputChange('includeKeywords', e.target.value)}
rows={2}
/>
</div>
<div>
<Label htmlFor="excludeKeywords"></Label>
<textarea
id="excludeKeywords"
className="w-full border rounded p-2 text-sm"
placeholder="多个关键词用逗号或换行分隔"
value={formData.excludeKeywords}
onChange={e => handleInputChange('excludeKeywords', e.target.value)}
rows={2}
/>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.name}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.selectedDevices.length} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.selectedGroups.length} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.likeInterval} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.maxLikesPerDay} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.friendMaxLikes} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.startTime} - {formData.endTime}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.contentTypes.map(t => ({text:'文字',image:'图片',video:'视频',link:'链接'}[t])).join('、')}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.includeKeywords}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">:</span>
<span>{formData.excludeKeywords}</span>
<Label></Label>
<div className="flex flex-wrap gap-2 mt-2">
{(['text', 'image', 'video', 'link'] as ContentType[]).map((type) => (
<Badge
key={type}
variant={formData.contentTypes.includes(type) ? "default" : "outline"}
className="cursor-pointer"
onClick={() => handleContentTypeToggle(type)}
>
{type === 'text' ? '文字' : type === 'image' ? '图片' : type === 'video' ? '视频' : '链接'}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center mb-2">
<div className="flex items-center space-x-2">
<Switch
checked={formData.enableFriendTags}
onCheckedChange={v => handleInputChange('enableFriendTags', v)}
className="mr-2"
onCheckedChange={(checked) => handleInputChange('enableFriendTags', checked)}
/>
<span className="text-sm"></span>
<Label htmlFor="enableFriendTags"></Label>
</div>
{formData.enableFriendTags && (
<Input
placeholder="请输入标签,多个标签用逗号分隔"
value={formData.friendTags}
onChange={e => handleInputChange('friendTags', e.target.value)}
/>
<div>
<Label htmlFor="friendTags"></Label>
<Input
id="friendTags"
value={formData.friendTags}
onChange={(e) => handleInputChange('friendTags', e.target.value)}
placeholder="请输入要添加的标签"
/>
</div>
)}
</CardContent>
</Card>
</div>
)}
{/* 底部按钮 */}
{/* 操作按钮 */}
<div className="flex justify-between mt-6">
<Button
variant="outline"
@@ -523,6 +431,7 @@ export default function NewAutoLike() {
>
</Button>
<div className="flex space-x-2">
{currentStep < 3 ? (
<Button onClick={handleNext}>

View File

@@ -0,0 +1,117 @@
// 自动点赞任务状态
export type LikeTaskStatus = 'running' | 'paused' | 'completed';
// 内容类型
export type ContentType = 'text' | 'image' | 'video' | 'link';
// 设备信息
export interface Device {
id: string;
name: string;
status: 'online' | 'offline';
lastActive: string;
}
// 好友信息
export interface Friend {
id: string;
nickname: string;
wechatId: string;
avatar: string;
tags: string[];
region: string;
source: string;
}
// 点赞记录
export interface LikeRecord {
id: string;
workbenchId: string;
momentsId: string;
snsId: string;
wechatAccountId: string;
wechatFriendId: string;
likeTime: string;
content: string;
resUrls: string;
momentTime: string;
userName: string;
operatorName: string;
operatorAvatar: string;
friendName: string;
friendAvatar: string;
}
// 自动点赞任务
export interface LikeTask {
id: string;
name: string;
status: LikeTaskStatus;
deviceCount: number;
targetGroup: string;
likeCount: number;
lastLikeTime: string;
createTime: string;
creator: string;
likeInterval: number;
maxLikesPerDay: number;
timeRange: { start: string; end: string };
contentTypes: ContentType[];
targetTags: string[];
devices: string[];
friends: string[];
friendMaxLikes: number;
friendTags: string;
enableFriendTags: boolean;
todayLikeCount: number;
totalLikeCount: number;
}
// 创建任务数据
export interface CreateLikeTaskData {
name: string;
interval: number;
maxLikes: number;
startTime: string;
endTime: string;
contentTypes: ContentType[];
devices: string[];
friends?: string[];
friendMaxLikes: number;
friendTags?: string;
enableFriendTags: boolean;
targetTags: string[];
}
// 更新任务数据
export interface UpdateLikeTaskData extends CreateLikeTaskData {
id: string;
}
// 任务配置
export interface TaskConfig {
interval: number;
maxLikes: number;
startTime: string;
endTime: string;
contentTypes: ContentType[];
devices: string[];
friends: string[];
friendMaxLikes: number;
friendTags: string;
enableFriendTags: boolean;
}
// API 响应格式
export interface ApiResponse<T = any> {
code: number;
msg: string;
data: T;
}
export interface PaginatedResponse<T> {
list: T[];
total: number;
page: number;
limit: number;
}