feat: 本次提交更新内容如下
朋友圈同步列表完成
This commit is contained in:
@@ -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 <PlaceholderPage title="朋友圈同步详情" />;
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [task, setTask] = useState<MomentsSyncTask | null>(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 (
|
||||
<Layout>
|
||||
<div className={style.detailLoading}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
return (
|
||||
<Layout>
|
||||
<div className={style.detailLoading}>
|
||||
<div>任务不存在</div>
|
||||
<Button onClick={() => navigate("/workspace/moments-sync")}>
|
||||
返回列表
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<div className={style.headerBar}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate("/workspace/moments-sync")}
|
||||
className={style.backBtn}
|
||||
/>
|
||||
<span className={style.title}>任务详情</span>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={handleEdit}
|
||||
className={style.editBtn}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={style.detailBg}>
|
||||
<div className={style.detailCard}>
|
||||
<div className={style.detailTop}>
|
||||
<div className={style.detailTitle}>{task.name}</div>
|
||||
<span
|
||||
className={
|
||||
task.status === 1
|
||||
? style.statusPill + " " + style.statusActive
|
||||
: style.statusPill + " " + style.statusPaused
|
||||
}
|
||||
>
|
||||
{getStatusText(task.status)}
|
||||
</span>
|
||||
<Switch
|
||||
checked={task.status === 1}
|
||||
onChange={handleToggleStatus}
|
||||
className={style.switchBtn}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div className={style.detailInfoRow}>
|
||||
<div className={style.infoCol}>
|
||||
推送设备:{task.config?.devices?.length || 0} 个
|
||||
</div>
|
||||
<div className={style.infoCol}>
|
||||
内容库:{task.config?.contentLibraryNames?.join(",") || "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.detailInfoRow}>
|
||||
<div className={style.infoCol}>
|
||||
已同步:{task.syncCount || 0} 条
|
||||
</div>
|
||||
<div className={style.infoCol}>创建人:{task.creatorName}</div>
|
||||
</div>
|
||||
<div className={style.detailBottom}>
|
||||
<div className={style.bottomLeft}>
|
||||
<ClockCircleOutlined className={style.clockIcon} />
|
||||
上次同步:{task.lastSyncTime || "无"}
|
||||
</div>
|
||||
<div className={style.bottomRight}>创建时间:{task.createTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 可继续补充更多详情卡片,如同步设置、同步记录等 */}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default MomentsSyncDetail;
|
||||
|
||||
@@ -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<MomentsSyncTask[]>([]);
|
||||
|
||||
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) => (
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
key="view"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => navigate(`/workspace/moments-sync/${task.id}`)}
|
||||
>
|
||||
查看
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="edit"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => navigate(`/workspace/moments-sync/edit/${task.id}`)}
|
||||
>
|
||||
编辑
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="copy"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => handleCopy(task.id)}
|
||||
>
|
||||
复制
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="delete"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleDelete(task.id)}
|
||||
danger
|
||||
>
|
||||
删除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<PlaceholderPage
|
||||
title="朋友圈同步"
|
||||
showAddButton
|
||||
addButtonText="新建同步"
|
||||
/>
|
||||
<Layout
|
||||
header={
|
||||
<>
|
||||
<div className={style.headerBar}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate("/workspace")}
|
||||
className={style.backBtn}
|
||||
/>
|
||||
<span className={style.title}>朋友圈同步</span>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => navigate("/workspace/moments-sync/new")}
|
||||
className={style.addBtn}
|
||||
>
|
||||
新建任务
|
||||
</Button>
|
||||
</div>
|
||||
<div className={style.searchBar}>
|
||||
<Input
|
||||
placeholder="搜索任务名称"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onPressEnter={fetchTasks}
|
||||
className={style.searchInput}
|
||||
/>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={fetchTasks}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={style.pageBg}>
|
||||
<div className={style.taskList}>
|
||||
{filteredTasks.length === 0 ? (
|
||||
<div className={style.emptyBox}>
|
||||
<span style={{ fontSize: 40, color: "#ddd" }}>
|
||||
<ClockCircleOutlined />
|
||||
</span>
|
||||
<div className={style.emptyText}>暂无同步任务</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => navigate("/workspace/moments-sync/new")}
|
||||
>
|
||||
新建第一个任务
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
filteredTasks.map((task) => (
|
||||
<div key={task.id} className={style.itemCard}>
|
||||
<div className={style.itemTop}>
|
||||
<div className={style.itemTitle}>
|
||||
<span className={style.itemName}>{task.name}</span>
|
||||
<span
|
||||
className={
|
||||
task.status === 1
|
||||
? style.statusPill + " " + style.statusActive
|
||||
: style.statusPill + " " + style.statusPaused
|
||||
}
|
||||
>
|
||||
{getStatusText(task.status)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style.itemActions}>
|
||||
<Switch
|
||||
checked={task.status === 1}
|
||||
onChange={() => handleToggle(task.id, task.status)}
|
||||
className={style.switchBtn}
|
||||
size="small"
|
||||
/>
|
||||
<Dropdown
|
||||
overlay={getMenu(task)}
|
||||
trigger={["click"]}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MoreOutlined />}
|
||||
className={style.moreBtn}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.itemInfoRow}>
|
||||
<div className={style.infoCol}>
|
||||
推送设备:{task.config?.devices?.length || 0} 个
|
||||
</div>
|
||||
<div className={style.infoCol}>
|
||||
已同步:{task.syncCount || 0} 条
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.itemInfoRow}>
|
||||
<div className={style.infoCol}>
|
||||
内容库:
|
||||
{task.config?.contentLibraryNames?.join(",") ||
|
||||
task.contentLib ||
|
||||
"默认内容库"}
|
||||
</div>
|
||||
<div className={style.infoCol}>
|
||||
创建人:{task.creatorName}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.itemBottom}>
|
||||
<div className={style.bottomLeft}>
|
||||
<ClockCircleOutlined className={style.clockIcon} />
|
||||
上次同步:{task.lastSyncTime || "无"}
|
||||
</div>
|
||||
<div className={style.bottomRight}>
|
||||
创建时间:{task.createTime}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
387
nkebao/src/pages/workspace/moments-sync/index.module.scss
Normal file
387
nkebao/src/pages/workspace/moments-sync/index.module.scss
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user