diff --git a/nkebao/src/pages/workspace/moments-sync/Detail.tsx b/nkebao/src/pages/workspace/moments-sync/Detail.tsx index b46fd03c..7b6f2eb8 100644 --- a/nkebao/src/pages/workspace/moments-sync/Detail.tsx +++ b/nkebao/src/pages/workspace/moments-sync/Detail.tsx @@ -1,8 +1,186 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; +import React, { useState, useEffect, useCallback } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { Button, Switch, message, Spin, Badge } from "antd"; +import { + ArrowLeftOutlined, + EditOutlined, + ClockCircleOutlined, + DatabaseOutlined, + MobileOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import style from "./index.module.scss"; +import request from "@/api/request"; + +interface MomentsSyncTask { + id: string; + name: string; + status: 1 | 2; + deviceCount: number; + syncCount: number; + lastSyncTime: string; + createTime: string; + creatorName: string; + updateTime?: string; + maxSyncPerDay?: number; + syncInterval?: number; + timeRange?: { start: string; end: string }; + contentTypes?: string[]; + targetTags?: string[]; + todaySyncCount?: number; + totalSyncCount?: number; + syncMode?: string; + config?: { + devices?: string[]; + contentLibraryNames?: string[]; + syncCount?: number; + }; +} + +const getStatusText = (status: number) => { + switch (status) { + case 1: + return "进行中"; + case 2: + return "已暂停"; + default: + return "未知"; + } +}; const MomentsSyncDetail: React.FC = () => { - return ; + const { id } = useParams(); + const navigate = useNavigate(); + const [task, setTask] = useState(null); + const [loading, setLoading] = useState(false); + + const fetchTaskDetail = useCallback(async () => { + if (!id) return; + setLoading(true); + try { + const res = await request("/v1/workbench/detail", { id }, "GET"); + if (res) setTask(res); + } catch { + message.error("获取任务详情失败"); + } finally { + setLoading(false); + } + }, [id]); + + useEffect(() => { + if (id) fetchTaskDetail(); + }, [id, fetchTaskDetail]); + + const handleToggleStatus = async () => { + if (!task || !id) return; + try { + const newStatus = task.status === 1 ? 2 : 1; + await request( + "/v1/workbench/update-status", + { id, status: newStatus }, + "POST" + ); + setTask({ ...task, status: newStatus }); + message.success(newStatus === 1 ? "任务已开启" : "任务已暂停"); + } catch { + message.error("操作失败"); + } + }; + + const handleEdit = () => { + if (id) navigate(`/workspace/moments-sync/edit/${id}`); + }; + + if (loading) { + return ( + +
+ +
+
+ ); + } + + if (!task) { + return ( + +
+
任务不存在
+ +
+
+ ); + } + + return ( + + + + } + > +
+
+
+
{task.name}
+ + {getStatusText(task.status)} + + +
+
+
+ 推送设备:{task.config?.devices?.length || 0} 个 +
+
+ 内容库:{task.config?.contentLibraryNames?.join(",") || "-"} +
+
+
+
+ 已同步:{task.syncCount || 0} 条 +
+
创建人:{task.creatorName}
+
+
+
+ + 上次同步:{task.lastSyncTime || "无"} +
+
创建时间:{task.createTime}
+
+
+ {/* 可继续补充更多详情卡片,如同步设置、同步记录等 */} +
+
+ ); }; export default MomentsSyncDetail; diff --git a/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx b/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx index b4564f83..ed9c899e 100644 --- a/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx +++ b/nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx @@ -1,13 +1,273 @@ -import React from "react"; -import PlaceholderPage from "@/components/PlaceholderPage"; +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { Button, Switch, Input, message, Badge, Dropdown, Menu } from "antd"; +import { + PlusOutlined, + SearchOutlined, + ReloadOutlined, + EyeOutlined, + EditOutlined, + DeleteOutlined, + CopyOutlined, + MoreOutlined, + ClockCircleOutlined, + ArrowLeftOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import style from "./index.module.scss"; +import request from "@/api/request"; + +interface MomentsSyncTask { + id: string; + name: string; + status: 1 | 2; + deviceCount: number; + syncCount: number; + lastSyncTime: string; + createTime: string; + creatorName: string; + contentLib?: string; + config?: { devices?: string[]; contentLibraryNames?: string[] }; +} + +const getStatusText = (status: number) => { + switch (status) { + case 1: + return "进行中"; + case 2: + return "已暂停"; + default: + return "未知"; + } +}; const MomentsSync: React.FC = () => { + const navigate = useNavigate(); + const [searchTerm, setSearchTerm] = useState(""); + const [loading, setLoading] = useState(false); + const [tasks, setTasks] = useState([]); + + const fetchTasks = async () => { + setLoading(true); + try { + const res = await request( + "/v1/workbench/list", + { type: 2, page: 1, limit: 100 }, + "GET" + ); + setTasks(res.list || []); + } catch (e) { + message.error("获取任务失败"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchTasks(); + }, []); + + const handleDelete = async (id: string) => { + if (!window.confirm("确定要删除该任务吗?")) return; + try { + await request("/v1/workbench/delete", { id }, "DELETE"); + message.success("删除成功"); + fetchTasks(); + } catch { + message.error("删除失败"); + } + }; + + const handleCopy = async (id: string) => { + try { + await request("/v1/workbench/copy", { id }, "POST"); + message.success("复制成功"); + fetchTasks(); + } catch { + message.error("复制失败"); + } + }; + + const handleToggle = async (id: string, status: number) => { + const newStatus = status === 1 ? 2 : 1; + try { + await request( + "/v1/workbench/update-status", + { id, status: newStatus }, + "POST" + ); + setTasks((prev) => + prev.map((t) => (t.id === id ? { ...t, status: newStatus } : t)) + ); + message.success("操作成功"); + } catch { + message.error("操作失败"); + } + }; + + const filteredTasks = tasks.filter((task) => + task.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // 菜单 + const getMenu = (task: MomentsSyncTask) => ( + + } + onClick={() => navigate(`/workspace/moments-sync/${task.id}`)} + > + 查看 + + } + onClick={() => navigate(`/workspace/moments-sync/edit/${task.id}`)} + > + 编辑 + + } + onClick={() => handleCopy(task.id)} + > + 复制 + + } + onClick={() => handleDelete(task.id)} + danger + > + 删除 + + + ); + return ( - + +
+ +
+
+ } + value={searchTerm} + onChange={(e) => setSearchTerm(e.target.value)} + onPressEnter={fetchTasks} + className={style.searchInput} + /> +
+ + } + > +
+
+ {filteredTasks.length === 0 ? ( +
+ + + +
暂无同步任务
+ +
+ ) : ( + filteredTasks.map((task) => ( +
+
+
+ {task.name} + + {getStatusText(task.status)} + +
+
+ handleToggle(task.id, task.status)} + className={style.switchBtn} + size="small" + /> + +
+
+
+
+ 推送设备:{task.config?.devices?.length || 0} 个 +
+
+ 已同步:{task.syncCount || 0} 条 +
+
+
+
+ 内容库: + {task.config?.contentLibraryNames?.join(",") || + task.contentLib || + "默认内容库"} +
+
+ 创建人:{task.creatorName} +
+
+
+
+ + 上次同步:{task.lastSyncTime || "无"} +
+
+ 创建时间:{task.createTime} +
+
+
+ )) + )} +
+
+
); }; diff --git a/nkebao/src/pages/workspace/moments-sync/index.module.scss b/nkebao/src/pages/workspace/moments-sync/index.module.scss new file mode 100644 index 00000000..a1993498 --- /dev/null +++ b/nkebao/src/pages/workspace/moments-sync/index.module.scss @@ -0,0 +1,387 @@ +.pageBg { + background: #f8f6f3; + padding-bottom: 24px; +} + +.headerBar { + display: flex; + align-items: center; + justify-content: space-between; + background: #fff; + border-bottom: 1px solid #f0f0f0; + padding: 0 16px; + height: 56px; +} + +.title { + font-size: 18px; + font-weight: bold; + color: #188eee; +} + +.backBtn { + border: none; + background: none; + box-shadow: none; + color: #666; + font-size: 18px; + margin-right: 8px; +} + +.addBtn { + margin-left: 8px; +} + +.searchBar { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 16px 0 16px; + background: #f8f6f3; +} + +.searchInput { + flex: 1; + min-width: 0; +} + +.taskList { + padding: 16px; +} + +.taskCard { + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + margin-bottom: 16px; + padding: 16px; + transition: box-shadow 0.2s; + &:hover { + box-shadow: 0 4px 16px rgba(24,142,238,0.10); + } +} + +.taskCardTop { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; +} + +.taskName { + font-size: 16px; + font-weight: 500; + color: #222; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.switchBtn { + margin-left: 8px; +} + +.actionBtn { + margin-left: 4px; +} + +.taskCardInfo { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 4px 16px; + font-size: 13px; + color: #666; + margin-top: 8px; +} + +.emptyBox { + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + padding: 40px 0 32px 0; + text-align: center; + color: #bbb; + margin-top: 40px; +} + +.emptyText { + font-size: 16px; + color: #888; + margin: 16px 0 20px 0; +} + +.itemCard { + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + margin-bottom: 16px; + padding: 16px 16px 12px 16px; + transition: box-shadow 0.2s; + position: relative; + &:hover { + box-shadow: 0 4px 16px rgba(24,142,238,0.10); + } +} + +.itemTop { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.itemTitle { + display: flex; + align-items: center; + gap: 8px; + font-size: 15px; + font-weight: 500; + color: #222; +} + +.itemName { + font-size: 15px; + font-weight: 500; + color: #222; + margin-right: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; +} + +.statusBadge { + margin-left: 2px; + .ant-badge-status-dot { + width: 8px; + height: 8px; + } + .ant-badge-status-success { + background: #19c37d; + } + .ant-badge-status-default { + background: #bdbdbd; + } + .ant-badge-status-text { + font-size: 12px; + font-weight: 400; + padding: 0 6px; + border-radius: 8px; + background: #f5f5f5; + color: #222; + } +} + +.itemActions { + display: flex; + align-items: center; + gap: 6px; +} + +.switchBtn { + margin-right: 2px; +} + +.moreBtn { + margin-left: 2px; + color: #888; + font-size: 18px; + background: none; + border: none; + box-shadow: none; +} + +.itemInfoRow { + display: flex; + font-size: 13px; + color: #666; + margin-bottom: 2px; +} +.infoCol { + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.itemBottom { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 12px; + color: #888; + border-top: 1px solid #f3f3f3; + margin-top: 8px; + padding-top: 6px; +} +.bottomLeft { + display: flex; + align-items: center; + gap: 4px; +} +.clockIcon { + color: #bdbdbd; + font-size: 14px; + margin-right: 2px; +} +.bottomRight { + text-align: right; +} + +// 覆盖Antd Dropdown菜单样式 +.ant-dropdown-menu { + border-radius: 10px !important; + box-shadow: 0 4px 16px rgba(0,0,0,0.12) !important; + min-width: 110px !important; + padding: 6px 0 !important; +} +.ant-dropdown-menu-item { + font-size: 14px !important; + padding: 7px 16px !important; + border-radius: 6px !important; + display: flex; + align-items: center; + gap: 8px; + transition: background 0.2s; +} +.ant-dropdown-menu-item:hover { + background: #f5f5f5 !important; +} +.ant-dropdown-menu-item-danger { + color: #e53e3e !important; +} + +.detailBg { + background: #f8f6f3; + min-height: 100vh; + padding: 24px 0 32px 0; + display: flex; + flex-direction: column; + align-items: center; +} + +.detailCard { + background: #fff; + border-radius: 10px; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + padding: 20px 20px 12px 20px; + width: 100%; + max-width: 480px; + margin-bottom: 24px; +} + +.detailTop { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; +} + +.detailTitle { + font-size: 18px; + font-weight: 600; + color: #222; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.statusBadge { + margin-left: 2px; + .ant-badge-status-dot { + width: 8px; + height: 8px; + } + .ant-badge-status-success { + background: #19c37d; + } + .ant-badge-status-default { + background: #bdbdbd; + } + .ant-badge-status-text { + font-size: 12px; + font-weight: 400; + padding: 0 6px; + border-radius: 8px; + background: #f5f5f5; + color: #222; + } +} + +.switchBtn { + margin-left: 8px; +} + +.detailInfoRow { + display: flex; + font-size: 14px; + color: #666; + margin-bottom: 2px; +} +.infoCol { + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.detailBottom { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 12px; + color: #888; + border-top: 1px solid #f3f3f3; + margin-top: 10px; + padding-top: 6px; +} +.bottomLeft { + display: flex; + align-items: center; + gap: 4px; +} +.clockIcon { + color: #bdbdbd; + font-size: 14px; + margin-right: 2px; +} +.bottomRight { + text-align: right; +} + +.detailLoading { + min-height: 300px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #888; + font-size: 16px; + gap: 16px; +} + +.statusPill { + display: inline-block; + min-width: 48px; + height: 20px; + line-height: 20px; + font-size: 10px; + border-radius: 12px; + text-align: center; + margin-left: 6px; + box-sizing: border-box; +} +.statusActive { + background: #19c37d; + color: #fff; +} +.statusPaused { + background: #e5e7eb; + color: #888; +} \ No newline at end of file