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

开始迁移工作台了,有好多问题
This commit is contained in:
笔记本里的永平
2025-07-08 08:59:58 +08:00
parent 5d68a41931
commit 521a3f1269
11 changed files with 3436 additions and 3979 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Plus,
@@ -35,79 +35,52 @@ import PageHeader from '@/components/PageHeader';
import BottomNav from '@/components/BottomNav';
import { useToast } from '@/components/ui/toast';
import '@/components/Layout.css';
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 {
fetchAutoLikeTasks,
deleteAutoLikeTask,
toggleAutoLikeTask,
copyAutoLikeTask,
LikeTask,
} from '@/api/autoLike';
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[]>([
{
id: '1',
name: '高频互动点赞',
deviceCount: 2,
targetGroup: '高频互动好友',
likeCount: 156,
lastLikeTime: '2025-02-06 13:12:35',
createTime: '2024-11-20 19:04:14',
creator: 'admin',
status: 'running',
likeInterval: 5,
maxLikesPerDay: 200,
timeRange: { start: '08:00', end: '22:00' },
contentTypes: ['text', 'image', 'video'],
targetTags: ['高频互动', '高意向', '男性'],
},
{
id: '2',
name: '潜在客户点赞',
deviceCount: 1,
targetGroup: '潜在客户',
likeCount: 89,
lastLikeTime: '2024-03-04 14:09:35',
createTime: '2024-03-04 14:29:04',
creator: 'manager',
status: 'paused',
likeInterval: 10,
maxLikesPerDay: 150,
timeRange: { start: '09:00', end: '21:00' },
contentTypes: ['image', 'video'],
targetTags: ['潜在客户', '中意向', '女性'],
},
]);
const [tasks, setTasks] = useState<LikeTask[]>([]);
const [loading, setLoading] = useState(false);
// 获取任务列表
const fetchTasks = async () => {
setLoading(true);
try {
const list = await fetchAutoLikeTasks();
setTasks(list);
} catch {
toast({ title: '获取任务失败', variant: 'destructive' });
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchTasks();
}, []);
const toggleExpand = (taskId: string) => {
setExpandedTaskId(expandedTaskId === taskId ? null : taskId);
};
const handleDelete = (taskId: string) => {
const taskToDelete = tasks.find((task) => task.id === taskId);
if (!taskToDelete) return;
if (!window.confirm(`确定要删除"${taskToDelete.name}"吗?`)) return;
setTasks(tasks.filter((task) => task.id !== taskId));
toast({
title: '删除成功',
description: '已成功删除点赞任务',
});
const handleDelete = async (id: string) => {
if (!window.confirm('确定要删除该任务吗?')) return;
try {
await deleteAutoLikeTask(id);
toast({ title: '删除成功' });
fetchTasks();
} catch {
toast({ title: '删除失败', variant: 'destructive' });
}
};
const handleEdit = (taskId: string) => {
@@ -118,37 +91,24 @@ export default function AutoLike() {
navigate(`/workspace/auto-like/${taskId}`);
};
const handleCopy = (taskId: string) => {
const taskToCopy = tasks.find((task) => task.id === taskId);
if (taskToCopy) {
const newTask = {
...taskToCopy,
id: `${Date.now()}`,
name: `${taskToCopy.name} (复制)`,
createTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
};
setTasks([...tasks, newTask]);
toast({
title: '复制成功',
description: '已成功复制点赞任务',
});
}
const handleCopy = async (id: string) => {
try {
await copyAutoLikeTask(id);
toast({ title: '复制成功' });
fetchTasks();
} catch {
toast({ title: '复制失败', variant: 'destructive' });
}
};
const toggleTaskStatus = (taskId: string) => {
const task = tasks.find((t) => t.id === taskId);
if (!task) return;
setTasks(
tasks.map((task) =>
task.id === taskId ? { ...task, status: task.status === 'running' ? 'paused' : 'running' } : task,
),
);
toast({
title: task.status === 'running' ? '已暂停' : '已启动',
description: `${task.name}任务${task.status === 'running' ? '已暂停' : '已启动'}`,
});
const toggleTaskStatus = async (id: string, status: string) => {
try {
await toggleAutoLikeTask(id, status);
toast({ title: '操作成功' });
fetchTasks();
} catch {
toast({ title: '操作失败', variant: 'destructive' });
}
};
const handleCreateNew = () => {
@@ -207,22 +167,29 @@ export default function AutoLike() {
<Input
placeholder="搜索任务名称"
className="pl-9"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Button variant="outline" size="icon">
{/* 移除筛选按钮 */}
{/* <Button variant="outline" size="icon">
<Filter className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon">
<RefreshCw 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">
{filteredTasks.length === 0 ? (
{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>
@@ -245,7 +212,7 @@ export default function AutoLike() {
<div className="flex items-center space-x-2">
<Switch
checked={task.status === 'running'}
onCheckedChange={() => toggleTaskStatus(task.id)}
onCheckedChange={() => toggleTaskStatus(task.id, task.status === 'running' ? 'paused' : 'running')}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>

View File

@@ -38,9 +38,12 @@ export default function NewAutoLike() {
description: '',
likeInterval: 30,
maxLikesPerDay: 100,
friendMaxLikes: 10,
startTime: '09:00',
endTime: '18:00',
contentTypes: ['text', 'image'],
includeKeywords: '',
excludeKeywords: '',
selectedDevices: [] as string[],
selectedGroups: [] as string[],
targetTags: [] as string[],
@@ -355,6 +358,14 @@ export default function NewAutoLike() {
onChange={(e) => handleInputChange('maxLikesPerDay', parseInt(e.target.value))}
/>
</div>
<div>
<Label htmlFor="friendMaxLikes"></Label>
<Input
id="friendMaxLikes"
value={formData.friendMaxLikes.toString()}
onChange={(e) => handleInputChange('friendMaxLikes', parseInt(e.target.value))}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
@@ -383,14 +394,15 @@ export default function NewAutoLike() {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
<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}
@@ -402,6 +414,28 @@ export default function NewAutoLike() {
</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>
@@ -431,13 +465,52 @@ export default function NewAutoLike() {
<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>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center mb-2">
<Switch
checked={formData.enableFriendTags}
onCheckedChange={v => handleInputChange('enableFriendTags', v)}
className="mr-2"
/>
<span className="text-sm"></span>
</div>
{formData.enableFriendTags && (
<Input
placeholder="请输入标签,多个标签用逗号分隔"
value={formData.friendTags}
onChange={e => handleInputChange('friendTags', e.target.value)}
/>
)}
</CardContent>
</Card>
</div>
)}

View File

@@ -0,0 +1,144 @@
import React, { useEffect, useState } from 'react';
import Layout from '@/components/Layout';
import PageHeader from '@/components/PageHeader';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Progress } from '@/components/ui/progress';
import { Badge } from '@/components/ui/badge';
import { RefreshCw, Search, RefreshCw as SyncIcon, Eye } from 'lucide-react';
import { fetchMomentsSyncDevices, syncMoments, syncAllMoments, fetchMomentsLog, MomentsSyncDevice } from '@/api/momentsSync';
import { useToast } from '@/components/ui/toast';
export default function MomentsSyncPage() {
const { toast } = useToast();
const [devices, setDevices] = useState<MomentsSyncDevice[]>([]);
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState('');
const [logModal, setLogModal] = useState<{ open: boolean; log: string; name: string }>({ open: false, log: '', name: '' });
const fetchDevices = async () => {
setLoading(true);
try {
const res = await fetchMomentsSyncDevices(search);
setDevices(res.data?.list || []);
} catch {
toast({ title: '获取设备失败', variant: 'destructive' });
} finally {
setLoading(false);
}
};
useEffect(() => { fetchDevices(); }, []);
const handleSearch = () => {
fetchDevices();
};
const handleRefresh = () => {
setSearch('');
fetchDevices();
};
const handleSync = async (id: string) => {
try {
await syncMoments(id);
toast({ title: '同步已发起' });
fetchDevices();
} catch {
toast({ title: '同步失败', variant: 'destructive' });
}
};
const handleSyncAll = async () => {
try {
await syncAllMoments();
toast({ title: '全部同步已发起' });
fetchDevices();
} catch {
toast({ title: '同步失败', variant: 'destructive' });
}
};
const handleViewLog = async (device: MomentsSyncDevice) => {
try {
const res = await fetchMomentsLog(device.id);
setLogModal({ open: true, log: res.data?.log || device.log || '暂无日志', name: device.name });
} catch {
setLogModal({ open: true, log: device.log || '暂无日志', name: device.name });
}
};
return (
<Layout
header={<PageHeader title="朋友圈同步" defaultBackPath="/workspace" />}
>
<div className="bg-gray-50 min-h-screen pb-20">
<div className="p-4 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={search}
onChange={e => setSearch(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleSearch(); }}
/>
</div>
<Button variant="outline" size="icon" onClick={handleRefresh}>
<RefreshCw className="h-4 w-4" />
</Button>
<Button onClick={handleSyncAll} variant="default" size="sm">
<SyncIcon className="h-4 w-4 mr-1" />
</Button>
</div>
<div className="p-4 space-y-4">
{loading ? (
<Card className="p-8 text-center">...</Card>
) : devices.length === 0 ? (
<Card className="p-8 text-center"></Card>
) : (
devices.map(device => (
<Card key={device.id} className="p-4 flex flex-col md:flex-row md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2 mb-2">
<span className="font-medium text-base">{device.name}</span>
<Badge variant={
device.status === 'success' ? 'success' :
device.status === 'syncing' ? 'default' :
device.status === 'error' ? 'destructive' : 'outline'
}>
{device.status === 'success' ? '已完成' :
device.status === 'syncing' ? '同步中' :
device.status === 'error' ? '失败' : '待同步'}
</Badge>
</div>
<div className="text-xs text-gray-500 mb-2">{device.lastSyncTime || '无'}</div>
<Progress value={device.progress} className="h-2 mb-2" />
</div>
<div className="flex items-center space-x-2 mt-2 md:mt-0">
<Button size="sm" variant="outline" onClick={() => handleSync(device.id)} disabled={device.status === 'syncing'}>
<SyncIcon className="h-4 w-4 mr-1" />
</Button>
<Button size="sm" variant="ghost" onClick={() => handleViewLog(device)}>
<Eye className="h-4 w-4 mr-1" />
</Button>
</div>
</Card>
))
)}
</div>
{/* 日志弹窗 */}
{logModal.open && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-40">
<div className="bg-white rounded-lg shadow-lg max-w-lg w-full p-6 relative">
<div className="font-bold mb-2">{logModal.name} - </div>
<pre className="bg-gray-100 rounded p-2 text-xs max-h-60 overflow-auto whitespace-pre-wrap">{logModal.log}</pre>
<Button className="absolute top-2 right-2" size="icon" variant="ghost" onClick={() => setLogModal({ open: false, log: '', name: '' })}></Button>
</div>
</div>
)}
</div>
</Layout>
);
}