FEAT => 本次更新项目为:
同步
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.headerSearchInputWrap {
|
||||
position: relative;
|
||||
@@ -62,7 +62,7 @@
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
.contentWrap {
|
||||
padding: 12px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
@@ -10,10 +10,15 @@
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
height: 38px;
|
||||
width: 40px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
// 只针对当前模块的refresh-btn按钮进行样式设置
|
||||
&.ant-btn {
|
||||
height: 38px !important;
|
||||
width: 40px !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 8px !important;
|
||||
min-width: 40px !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.taskList {
|
||||
@@ -59,12 +64,11 @@
|
||||
}
|
||||
|
||||
.taskInfoGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px 16px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.progressBlock {
|
||||
@@ -100,6 +104,56 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
// CardMenu 样式
|
||||
.menu-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-dropdown {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 28px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
z-index: 100;
|
||||
min-width: 120px;
|
||||
padding: 4px;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
gap: 8px;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #ff4d4f;
|
||||
|
||||
&:hover {
|
||||
background: #fff2f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.taskCard {
|
||||
padding: 12px 6px 8px 6px;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { NavBar } from "antd-mobile";
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
TeamOutlined,
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
ReloadOutlined,
|
||||
@@ -12,25 +11,12 @@ import {
|
||||
DeleteOutlined,
|
||||
EyeOutlined,
|
||||
CopyOutlined,
|
||||
DownOutlined,
|
||||
UpOutlined,
|
||||
SettingOutlined,
|
||||
CalendarOutlined,
|
||||
TeamOutlined,
|
||||
MessageOutlined,
|
||||
SendOutlined,
|
||||
CarryOutOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Input,
|
||||
Badge,
|
||||
Switch,
|
||||
Progress,
|
||||
Dropdown,
|
||||
Menu,
|
||||
} from "antd";
|
||||
import { Card, Button, Input, Badge, Switch } from "antd";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import {
|
||||
fetchGroupPushTasks,
|
||||
deleteGroupPushTask,
|
||||
@@ -39,6 +25,71 @@ import {
|
||||
} from "./index.api";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
// 卡片菜单组件
|
||||
interface CardMenuProps {
|
||||
onView: () => void;
|
||||
onEdit: () => void;
|
||||
onCopy: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
const CardMenu: React.FC<CardMenuProps> = ({ onEdit, onCopy, onDelete }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
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)} className={styles["menu-btn"]}>
|
||||
<MoreOutlined />
|
||||
</button>
|
||||
{open && (
|
||||
<div ref={menuRef} className={styles["menu-dropdown"]}>
|
||||
<div
|
||||
onClick={() => {
|
||||
onEdit();
|
||||
setOpen(false);
|
||||
}}
|
||||
className={styles["menu-item"]}
|
||||
>
|
||||
<EditOutlined />
|
||||
编辑
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
onCopy();
|
||||
setOpen(false);
|
||||
}}
|
||||
className={styles["menu-item"]}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
setOpen(false);
|
||||
}}
|
||||
className={`${styles["menu-item"]} ${styles["danger"]}`}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
删除
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GroupPush: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [expandedTaskId, setExpandedTaskId] = useState<string | null>(null);
|
||||
@@ -71,7 +122,7 @@ const GroupPush: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleEdit = (taskId: string) => {
|
||||
navigate(`/workspace/group-push/${taskId}/edit`);
|
||||
navigate(`/workspace/group-push/edit/${taskId}`);
|
||||
};
|
||||
|
||||
const handleView = (taskId: string) => {
|
||||
@@ -121,68 +172,45 @@ const GroupPush: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getMessageTypeText = (type: string) => {
|
||||
switch (type) {
|
||||
case "text":
|
||||
return "文字";
|
||||
case "image":
|
||||
return "图片";
|
||||
case "video":
|
||||
return "视频";
|
||||
case "link":
|
||||
return "链接";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
};
|
||||
|
||||
const getSuccessRate = (pushCount: number, successCount: number) => {
|
||||
if (pushCount === 0) return 0;
|
||||
return Math.round((successCount / pushCount) * 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout
|
||||
loading={loading}
|
||||
header={
|
||||
<NavBar
|
||||
back={null}
|
||||
left={
|
||||
<div className="nav-title">
|
||||
<ArrowLeftOutlined
|
||||
twoToneColor="#1677ff"
|
||||
onClick={() => navigate(-1)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
style={{ background: "#fff" }}
|
||||
right={
|
||||
<>
|
||||
<NavCommon
|
||||
title="群消息推送"
|
||||
right={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleCreateNew}
|
||||
>
|
||||
创建任务
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className={styles.searchBar}>
|
||||
<Input
|
||||
placeholder="搜索计划名称"
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
prefix={<SearchOutlined />}
|
||||
allowClear
|
||||
size="large"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleCreateNew}
|
||||
onClick={fetchTasks}
|
||||
size="large"
|
||||
className={styles["refresh-btn"]}
|
||||
>
|
||||
创建任务
|
||||
<ReloadOutlined />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<span className="nav-title">群消息推送</span>
|
||||
</NavBar>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={styles.bg}>
|
||||
<div className={styles.searchBar}>
|
||||
<Input
|
||||
placeholder="搜索计划名称"
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
prefix={<SearchOutlined />}
|
||||
allowClear
|
||||
size="large"
|
||||
/>
|
||||
<Button size="small" onClick={fetchTasks} loading={loading}>
|
||||
<ReloadOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.taskList}>
|
||||
{filteredTasks.length === 0 ? (
|
||||
<Card className={styles.emptyCard}>
|
||||
@@ -220,49 +248,23 @@ const GroupPush: React.FC = () => {
|
||||
checked={task.status === 1}
|
||||
onChange={() => toggleTaskStatus(task.id)}
|
||||
/>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "view",
|
||||
icon: <EyeOutlined />,
|
||||
label: "查看",
|
||||
onClick: () => handleView(task.id),
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
icon: <EditOutlined />,
|
||||
label: "编辑",
|
||||
onClick: () => handleEdit(task.id),
|
||||
},
|
||||
{
|
||||
key: "copy",
|
||||
icon: <CopyOutlined />,
|
||||
label: "复制",
|
||||
onClick: () => handleCopy(task.id),
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
icon: <DeleteOutlined />,
|
||||
label: "删除",
|
||||
danger: true,
|
||||
onClick: () => handleDelete(task.id),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button icon={<MoreOutlined />} />
|
||||
</Dropdown>
|
||||
<CardMenu
|
||||
onView={() => handleView(task.id)}
|
||||
onEdit={() => handleEdit(task.id)}
|
||||
onCopy={() => handleCopy(task.id)}
|
||||
onDelete={() => handleDelete(task.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.taskInfoGrid}>
|
||||
<div>执行设备:{task.deviceCount || 1} 个</div>
|
||||
<div>目标群组:{task.config?.groups?.length || 0} 个</div>
|
||||
<div>
|
||||
推送成功:{task.successCount || 0}/{task.pushCount || 0}
|
||||
<TeamOutlined />
|
||||
推送目标:{task.config?.groups?.length || 0} 个社群
|
||||
</div>
|
||||
<div>
|
||||
<CarryOutOutlined /> 推送内容:
|
||||
{task.config?.content || 0} 个
|
||||
</div>
|
||||
<div>创建人:{task.creatorName || task.creator}</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.taskFooter}>
|
||||
|
||||
@@ -83,7 +83,7 @@ const workspaceRoutes = [
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/:id/edit",
|
||||
path: "/workspace/group-push/edit/:id",
|
||||
element: <FormGroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user