FEAT => 本次更新项目为:

同步
This commit is contained in:
超级老白兔
2025-08-06 18:29:12 +08:00
parent 7ebece0491
commit cd88e44464
4 changed files with 177 additions and 121 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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}>

View File

@@ -83,7 +83,7 @@ const workspaceRoutes = [
auth: true,
},
{
path: "/workspace/group-push/:id/edit",
path: "/workspace/group-push/edit/:id",
element: <FormGroupPush />,
auth: true,
},