You've already forked DataMate
## 功能概述 实现完整的任务拆分、分配和进度跟踪功能,支持将任务拆分为子任务并分配给不同用户。 ## Phase 1: 数据库层 - 新增 t_task_meta 表(任务元数据协调表) - 新增 t_task_assignment_log 表(分配日志表) - 新增 3 个权限条目(read/write/assign) - 新增 SQLAlchemy ORM 模型 ## Phase 2: 后端 API (Java) - 新增 task-coordination-service 模块(32 个文件) - 实现 11 个 API 端点: - 任务查询(列表、子任务、我的任务) - 任务拆分(支持 4 种策略) - 任务分配(单个、批量、重新分配、撤回) - 进度管理(查询、更新、聚合) - 分配日志 - 集成权限控制和路由规则 ## Phase 3: 前端 UI (React + TypeScript) - 新增 10 个文件(模型、API、组件、页面) - 实现 5 个核心组件: - SplitTaskDialog - 任务拆分对话框 - AssignTaskDialog - 任务分配对话框 - BatchAssignDialog - 批量分配对话框 - TaskProgressPanel - 进度面板 - AssignmentLogDrawer - 分配记录 - 实现 2 个页面: - TaskCoordination - 任务管理主页 - MyTasks - 我的任务页面 - 集成侧边栏菜单和路由 ## 问题修复 - 修复 getMyTasks 分页参数缺失 - 修复子任务 assignee 信息缺失(批量查询优化) - 修复 proportion 精度计算(余量分配) ## 技术亮点 - 零侵入设计:通过独立协调表实现,不修改现有模块 - 批量查询优化:避免 N+1 查询问题 - 4 种拆分策略:按比例/数量/文件/手动 - 进度自动聚合:子任务更新自动聚合到父任务 - 权限细粒度控制:read/write/assign 三级权限 ## 验证 - Maven 编译:✅ 零错误 - TypeScript 编译:✅ 零错误 - Vite 生产构建:✅ 成功
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Drawer, Table, Tag, Spin, Timeline, App } from "antd";
|
|
import { TaskAssignmentLogDto } from "../taskCoordination.model";
|
|
import { getAssignmentLogsUsingGet } from "../taskCoordination.api";
|
|
import { AssignmentActionMap } from "../taskCoordination.const";
|
|
|
|
interface AssignmentLogDrawerProps {
|
|
open: boolean;
|
|
taskId: string | null;
|
|
taskName?: string;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const actionColorMap: Record<string, string> = {
|
|
ASSIGN: "blue",
|
|
REASSIGN: "orange",
|
|
REVOKE: "red",
|
|
};
|
|
|
|
export default function AssignmentLogDrawer({
|
|
open,
|
|
taskId,
|
|
taskName,
|
|
onClose,
|
|
}: AssignmentLogDrawerProps) {
|
|
const { message } = App.useApp();
|
|
const [loading, setLoading] = useState(false);
|
|
const [logs, setLogs] = useState<TaskAssignmentLogDto[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (!open || !taskId) return;
|
|
setLoading(true);
|
|
getAssignmentLogsUsingGet(taskId)
|
|
.then((res: any) => {
|
|
setLogs(res?.data ?? []);
|
|
})
|
|
.catch(() => {
|
|
message.error("加载分配记录失败");
|
|
})
|
|
.finally(() => {
|
|
setLoading(false);
|
|
});
|
|
}, [open, taskId, message]);
|
|
|
|
return (
|
|
<Drawer
|
|
title={`分配记录:${taskName || ""}`}
|
|
open={open}
|
|
onClose={onClose}
|
|
width={520}
|
|
>
|
|
<Spin spinning={loading}>
|
|
{logs.length === 0 && !loading ? (
|
|
<div className="text-center text-gray-400 py-8">暂无分配记录</div>
|
|
) : (
|
|
<Timeline
|
|
items={logs.map((log) => ({
|
|
color: actionColorMap[log.action] || "gray",
|
|
children: (
|
|
<div className="pb-2">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<Tag
|
|
color={actionColorMap[log.action] || "default"}
|
|
>
|
|
{AssignmentActionMap[log.action] || log.action}
|
|
</Tag>
|
|
<span className="text-sm text-gray-500">
|
|
{log.createdAt}
|
|
</span>
|
|
</div>
|
|
<div className="text-sm">
|
|
<span className="text-gray-500">操作人:</span>
|
|
{log.operatorName || "-"}
|
|
{log.userName && (
|
|
<>
|
|
<span className="text-gray-500 ml-3">目标用户:</span>
|
|
{log.userName}
|
|
</>
|
|
)}
|
|
</div>
|
|
{log.remark && (
|
|
<div className="text-sm text-gray-500 mt-1">
|
|
备注:{log.remark}
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
}))}
|
|
/>
|
|
)}
|
|
</Spin>
|
|
</Drawer>
|
|
);
|
|
}
|