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

路由处理下
This commit is contained in:
笔记本里的永平
2025-07-18 22:46:06 +08:00
parent 0532c3c161
commit bf89197517
2 changed files with 278 additions and 104 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import {
Plus,
Search,
@@ -12,21 +12,21 @@ import {
Copy,
ChevronLeft,
Share2,
} 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 { useToast } from '@/components/ui/toast';
import '@/components/Layout.css';
import {
fetchMomentsSyncTasks,
deleteMomentsSyncTask,
toggleMomentsSyncTask,
} 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 { useToast } from "@/components/ui/toast";
import "@/components/Layout.css";
import {
fetchMomentsSyncTasks,
deleteMomentsSyncTask,
toggleMomentsSyncTask,
copyMomentsSyncTask,
MomentsSyncTask
} from '@/api/momentsSync';
MomentsSyncTask,
} from "@/api/momentsSync";
type CardMenuProps = {
onView: () => void;
@@ -51,7 +51,16 @@ function CardMenu({ onView, onEdit, onCopy, onDelete }: CardMenuProps) {
return (
<div style={{ position: "relative" }}>
<button onClick={() => setOpen((v) => !v)} style={{ background: "none", border: "none", padding: 0, margin: 0, cursor: "pointer" }}>
<button
onClick={() => setOpen((v) => !v)}
style={{
background: "none",
border: "none",
padding: 0,
margin: 0,
cursor: "pointer",
}}
>
<MoreVertical className="h-4 w-4" />
</button>
{open && (
@@ -69,17 +78,106 @@ function CardMenu({ onView, onEdit, onCopy, onDelete }: CardMenuProps) {
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
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
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
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
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>
)}
@@ -90,7 +188,7 @@ function CardMenu({ onView, onEdit, onCopy, onDelete }: CardMenuProps) {
export default function MomentsSync() {
const navigate = useNavigate();
const { toast } = useToast();
const [searchTerm, setSearchTerm] = useState('');
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(false);
const [tasks, setTasks] = useState<MomentsSyncTask[]>([]);
@@ -100,24 +198,24 @@ export default function MomentsSync() {
try {
const list = await fetchMomentsSyncTasks();
// 确保数据字段与界面一致
const mappedTasks = list.map(task => ({
const mappedTasks = list.map((task) => ({
...task,
// 确保字段名称和格式与界面一致
status: task.status || 2, // 默认为关闭状态
deviceCount: task.deviceCount || 0,
targetGroup: task.targetGroup || '默认人群',
targetGroup: task.targetGroup || "默认人群",
syncCount: task.todaySyncCount || task.syncCount || 0,
creatorName: task.creatorName || '未知',
lastSyncTime: task.lastSyncTime || '暂无',
createTime: task.createTime || '未知',
creatorName: task.creatorName || "未知",
lastSyncTime: task.lastSyncTime || "暂无",
createTime: task.createTime || "未知",
syncInterval: task.syncInterval || 30,
maxSyncPerDay: task.maxSyncPerDay || 100,
timeRange: task.timeRange || { start: '08:00', end: '22:00' },
contentTypes: task.contentTypes || ['text', 'image', 'video'],
timeRange: task.timeRange || { start: "08:00", end: "22:00" },
contentTypes: task.contentTypes || ["text", "image", "video"],
targetTags: task.targetTags || [],
syncMode: task.syncMode || 'auto',
syncMode: task.syncMode || "auto",
filterKeywords: task.filterKeywords || [],
contentLib: task.config?.contentLibraryNames?.join(',') || '默认内容库'
contentLib: task.config?.contentLibraryNames?.join(",") || "默认内容库",
}));
setTasks(mappedTasks);
} catch (error) {
@@ -137,17 +235,25 @@ export default function MomentsSync() {
if (!taskToDelete) return;
if (!window.confirm(`确定要删除"${taskToDelete.name}"吗?`)) return;
try {
const response = await deleteMomentsSyncTask(id);
if (response.code === 200) {
toast({ title: "删除成功" });
fetchTasks();
} else {
toast({ title: "删除失败", description: response.msg || "请稍后重试", variant: "destructive" });
toast({
title: "删除失败",
description: response.msg || "请稍后重试",
variant: "destructive",
});
}
} catch (error) {
toast({ title: "删除失败", description: "请稍后重试", variant: "destructive" });
toast({
title: "删除失败",
description: "请稍后重试",
variant: "destructive",
});
}
};
@@ -166,18 +272,26 @@ export default function MomentsSync() {
toast({ title: "复制成功" });
fetchTasks();
} else {
toast({ title: "复制失败", description: response.msg || "请稍后重试", variant: "destructive" });
toast({
title: "复制失败",
description: response.msg || "请稍后重试",
variant: "destructive",
});
}
} catch (error) {
toast({ title: "复制失败", description: "请稍后重试", variant: "destructive" });
toast({
title: "复制失败",
description: "请稍后重试",
variant: "destructive",
});
}
};
const toggleTaskStatus = async (id: string, status: number) => {
// 先更新本地状态
const newStatus = (status === 1 ? 2 : 1) as 1 | 2;
setTasks(prevTasks =>
prevTasks.map(task =>
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, status: newStatus } : task
)
);
@@ -189,40 +303,48 @@ export default function MomentsSync() {
// 成功时不刷新列表,保持本地状态
} else {
// 请求失败,回退本地状态
setTasks(prevTasks =>
prevTasks.map(task =>
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, status: status as 1 | 2 } : task
)
);
toast({ title: "操作失败", description: response.msg || "请稍后重试", variant: "destructive" });
toast({
title: "操作失败",
description: response.msg || "请稍后重试",
variant: "destructive",
});
}
} catch (error) {
// 请求异常,回退本地状态
setTasks(prevTasks =>
prevTasks.map(task =>
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === id ? { ...task, status: status as 1 | 2 } : task
)
);
toast({ title: "操作失败", description: "请稍后重试", variant: "destructive" });
toast({
title: "操作失败",
description: "请稍后重试",
variant: "destructive",
});
}
};
const handleCreateNew = () => {
navigate('/workspace/moments-sync/new');
navigate("/workspace/moments-sync/new");
};
const filteredTasks = tasks.filter((task) =>
task.name.toLowerCase().includes(searchTerm.toLowerCase()),
task.name.toLowerCase().includes(searchTerm.toLowerCase())
);
const getStatusText = (status: number) => {
switch (status) {
case 1:
return '进行中';
return "进行中";
case 2:
return '已暂停';
return "已暂停";
default:
return '未知';
return "未知";
}
};
@@ -231,13 +353,18 @@ export default function MomentsSync() {
<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)}>
<Button
variant="ghost"
size="icon"
onClick={() => navigate("/workspace")}
>
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="text-lg font-medium"></h1>
</div>
<Button onClick={handleCreateNew}>
<Plus className="h-4 w-4 mr-2" />
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
</header>
@@ -246,14 +373,19 @@ export default function MomentsSync() {
<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"
<Input
placeholder="搜索任务名称"
className="pl-9"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<Button variant="outline" size="icon" onClick={fetchTasks} disabled={loading}>
<Button
variant="outline"
size="icon"
onClick={fetchTasks}
disabled={loading}
>
{loading ? (
<RefreshCw className="h-4 w-4 animate-spin" />
) : (
@@ -267,8 +399,12 @@ export default function MomentsSync() {
{filteredTasks.length === 0 ? (
<Card className="p-8 text-center">
<Share2 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>
<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" />
@@ -280,14 +416,20 @@ export default function MomentsSync() {
<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={Number(task.status) === 1 ? "success" : "secondary"}>
<Badge
variant={
Number(task.status) === 1 ? "success" : "secondary"
}
>
{getStatusText(task.status)}
</Badge>
</div>
<div className="flex items-center space-x-2">
<Switch
checked={Number(task.status) === 1}
onCheckedChange={() => toggleTaskStatus(task.id, Number(task.status))}
onCheckedChange={() =>
toggleTaskStatus(task.id, Number(task.status))
}
/>
<CardMenu
onView={() => handleView(task.id)}
@@ -300,11 +442,14 @@ export default function MomentsSync() {
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="text-sm text-gray-500">
<div>{task?.config?.devices.length||0} </div>
<div>{task?.config?.devices.length || 0} </div>
<div className="flex">
<span className="flex-shrink-0"></span>
<span className="truncate" title={task.contentLib || '默认内容库'}>
{task.contentLib || '默认内容库'}
<span
className="truncate"
title={task.contentLib || "默认内容库"}
>
{task.contentLib || "默认内容库"}
</span>
</div>
</div>
@@ -328,4 +473,4 @@ export default function MomentsSync() {
</div>
</div>
);
}
}

View File

@@ -1,22 +1,27 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
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 { fetchMomentsSyncTasks, syncMoments, syncAllMoments, MomentsSyncTask } from '@/api/momentsSync';
import { useToast } from '@/components/ui/toast';
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
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 {
fetchMomentsSyncTasks,
syncMoments,
syncAllMoments,
MomentsSyncTask,
} from "@/api/momentsSync";
import { useToast } from "@/components/ui/toast";
export default function MomentsSyncPage() {
const navigate = useNavigate();
const { toast } = useToast();
const [tasks, setTasks] = useState<MomentsSyncTask[]>([]);
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState('');
const [search, setSearch] = useState("");
const fetchTasks = async () => {
setLoading(true);
@@ -24,44 +29,46 @@ export default function MomentsSyncPage() {
const list = await fetchMomentsSyncTasks();
setTasks(list);
} catch {
toast({ title: '获取任务失败', variant: 'destructive' });
toast({ title: "获取任务失败", variant: "destructive" });
} finally {
setLoading(false);
}
};
useEffect(() => { fetchTasks(); }, []); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
fetchTasks();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleSearch = () => {
fetchTasks();
};
const handleRefresh = () => {
setSearch('');
setSearch("");
fetchTasks();
};
const handleSync = async (id: string) => {
try {
await syncMoments(id);
toast({ title: '同步已发起' });
toast({ title: "同步已发起" });
fetchTasks();
} catch {
toast({ title: '同步失败', variant: 'destructive' });
toast({ title: "同步失败", variant: "destructive" });
}
};
const handleSyncAll = async () => {
try {
await syncAllMoments();
toast({ title: '全部同步已发起' });
toast({ title: "全部同步已发起" });
fetchTasks();
} catch {
toast({ title: '同步失败', variant: 'destructive' });
toast({ title: "同步失败", variant: "destructive" });
}
};
const filteredTasks = tasks.filter(task =>
const filteredTasks = tasks.filter((task) =>
task.name.toLowerCase().includes(search.toLowerCase())
);
@@ -77,15 +84,18 @@ export default function MomentsSyncPage() {
placeholder="搜索任务名称"
className="pl-9"
value={search}
onChange={e => setSearch(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleSearch(); }}
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" />
<SyncIcon className="h-4 w-4 mr-1" />
</Button>
</div>
<div className="p-4 space-y-4">
@@ -94,26 +104,45 @@ export default function MomentsSyncPage() {
) : filteredTasks.length === 0 ? (
<Card className="p-8 text-center"></Card>
) : (
filteredTasks.map(task => (
<Card key={task.id} className="p-4 flex flex-col md:flex-row md:items-center md:justify-between">
filteredTasks.map((task) => (
<Card
key={task.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">{task.name}</span>
<Badge variant={
task.status === 1 ? 'success' : 'secondary'
}>
{task.status === 1 ? '进行中' : '已暂停'}
<Badge
variant={task.status === 1 ? "success" : "secondary"}
>
{task.status === 1 ? "进行中" : "已暂停"}
</Badge>
</div>
<div className="text-xs text-gray-500 mb-2">{task.lastSyncTime || '无'}</div>
<div className="text-xs text-gray-500 mb-2">{task.syncCount || 0} </div>
<div className="text-xs text-gray-500 mb-2">
{task.lastSyncTime || "无"}
</div>
<div className="text-xs text-gray-500 mb-2">
{task.syncCount || 0}
</div>
</div>
<div className="flex items-center space-x-2 mt-2 md:mt-0">
<Button size="sm" variant="outline" onClick={() => navigate(`/workspace/moments-sync/${task.id}`)}>
<Eye className="h-4 w-4 mr-1" />
<Button
size="sm"
variant="outline"
onClick={() =>
navigate(`/workspace/moments-sync/${task.id}`)
}
>
<Eye className="h-4 w-4 mr-1" />
</Button>
<Button size="sm" variant="outline" onClick={() => handleSync(task.id)}>
<SyncIcon className="h-4 w-4 mr-1" />
<Button
size="sm"
variant="outline"
onClick={() => handleSync(task.id)}
>
<SyncIcon className="h-4 w-4 mr-1" />
</Button>
</div>
</Card>
@@ -123,4 +152,4 @@ export default function MomentsSyncPage() {
</div>
</Layout>
);
}
}